diff --git a/go.sum b/go.sum index 8ab03fe0..e7c2b590 100644 --- a/go.sum +++ b/go.sum @@ -1062,6 +1062,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.5/go.mod h1:0zV5/ungglgy2Rlm3QK8fbxkXVs+BSJWpJP/+8gUVLY= +k8s.io/apimachinery v0.17.5 h1:QAjfgeTtSGksdkgyaPrIb4lhU16FWMIzxKejYD5S0gc= k8s.io/apimachinery v0.17.5/go.mod h1:ioIo1G/a+uONV7Tv+ZmCbMG1/a3kVw5YcDdncd8ugQ0= k8s.io/client-go v0.17.5/go.mod h1:S8uZpBpjJJdEH/fEyxcqg7Rn0P5jH+ilkgBHjriSmNo= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= diff --git a/internal/vendored/go-tools.dirs b/internal/vendored/go-tools.dirs index d68189d1..dd10fe7e 100644 --- a/internal/vendored/go-tools.dirs +++ b/internal/vendored/go-tools.dirs @@ -1,6 +1,9 @@ +event +fakenet jsonrpc2 lsp/debug/tag lsp/protocol span +stack telemetry xcontext diff --git a/internal/vendored/go-tools.version b/internal/vendored/go-tools.version index eef982bc..9f17b267 100644 --- a/internal/vendored/go-tools.version +++ b/internal/vendored/go-tools.version @@ -1 +1 @@ -gopls/v0.4.0-pre2 +gopls/v0.4.2 diff --git a/internal/vendored/go-tools/telemetry/bench_test.go b/internal/vendored/go-tools/event/bench_test.go similarity index 73% rename from internal/vendored/go-tools/telemetry/bench_test.go rename to internal/vendored/go-tools/event/bench_test.go index a93ad28d..162513f3 100644 --- a/internal/vendored/go-tools/telemetry/bench_test.go +++ b/internal/vendored/go-tools/event/bench_test.go @@ -1,4 +1,4 @@ -package telemetry_test +package event_test import ( "context" @@ -6,8 +6,11 @@ import ( "log" "testing" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) type Hooks struct { @@ -16,12 +19,12 @@ type Hooks struct { } var ( - aValue = event.NewIntKey("a", "") - bValue = event.NewStringKey("b", "") - aCount = event.NewInt64Key("aCount", "Count of time A is called.") - aStat = event.NewIntKey("aValue", "A value.") - bCount = event.NewInt64Key("B", "Count of time B is called.") - bLength = event.NewIntKey("BLen", "B length.") + 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()) { @@ -45,33 +48,33 @@ var ( Log = Hooks{ A: func(ctx context.Context, a int) (context.Context, func()) { - event.Print1(ctx, "A", aValue.Of(a)) + core.Log1(ctx, "A", aValue.Of(a)) return ctx, func() {} }, B: func(ctx context.Context, b string) (context.Context, func()) { - event.Print1(ctx, "B", bValue.Of(b)) + core.Log1(ctx, "B", bValue.Of(b)) return ctx, func() {} }, } Trace = Hooks{ A: func(ctx context.Context, a int) (context.Context, func()) { - return event.StartSpan1(ctx, "A", aValue.Of(a)) + return core.Start1(ctx, "A", aValue.Of(a)) }, B: func(ctx context.Context, b string) (context.Context, func()) { - return event.StartSpan1(ctx, "B", bValue.Of(b)) + return core.Start1(ctx, "B", bValue.Of(b)) }, } Stats = Hooks{ A: func(ctx context.Context, a int) (context.Context, func()) { - event.Record1(ctx, aStat.Of(a)) - event.Record1(ctx, aCount.Of(1)) + 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()) { - event.Record1(ctx, bLength.Of(len(b))) - event.Record1(ctx, bCount.Of(1)) + core.Metric1(ctx, bLength.Of(len(b))) + core.Metric1(ctx, bCount.Of(1)) return ctx, func() {} }, } @@ -146,6 +149,6 @@ func init() { log.SetOutput(ioutil.Discard) } -func noopExporter(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context { +func noopExporter(ctx context.Context, ev core.Event, lm label.Map) context.Context { return ctx } diff --git a/internal/vendored/go-tools/event/core/event.go b/internal/vendored/go-tools/event/core/event.go new file mode 100644 index 00000000..6892989d --- /dev/null +++ b/internal/vendored/go-tools/event/core/event.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" +) + +// Event holds the information about an event of note that ocurred. +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 +} diff --git a/internal/vendored/go-tools/telemetry/event/export.go b/internal/vendored/go-tools/event/core/export.go similarity index 75% rename from internal/vendored/go-tools/telemetry/event/export.go rename to internal/vendored/go-tools/event/core/export.go index 5b41e99e..c9143171 100644 --- a/internal/vendored/go-tools/telemetry/event/export.go +++ b/internal/vendored/go-tools/event/core/export.go @@ -2,18 +2,20 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package event +package core import ( "context" "sync/atomic" "time" "unsafe" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) // Exporter is a function that handles events. // It may return a modified context and event. -type Exporter func(context.Context, Event, TagMap) context.Context +type Exporter func(context.Context, Event, label.Map) context.Context var ( exporter unsafe.Pointer @@ -35,16 +37,16 @@ func SetExporter(e Exporter) { } // deliver is called to deliver an event to the supplied exporter. -// it will fill in the time and generate the basic tag source. +// 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() + ev.at = time.Now() // hand the event off to the current exporter - return exporter(ctx, ev, ev.Map()) + return exporter(ctx, ev, ev) } -// dispatch is called to deliver an event to the global exporter if set. -func dispatch(ctx context.Context, ev Event) context.Context { +// 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 { @@ -53,11 +55,11 @@ func dispatch(ctx context.Context, ev Event) context.Context { return deliver(ctx, *exporterPtr, ev) } -// dispatchPair is called to deliver a start event to the supplied exporter. +// 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 and generate the basic tag source. -func dispatchPair(ctx context.Context, begin, end Event) (context.Context, func()) { +// 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 { diff --git a/internal/vendored/go-tools/event/core/fast.go b/internal/vendored/go-tools/event/core/fast.go new file mode 100644 index 00000000..f01d89e5 --- /dev/null +++ b/internal/vendored/go-tools/event/core/fast.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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)) +} diff --git a/internal/vendored/go-tools/event/doc.go b/internal/vendored/go-tools/event/doc.go new file mode 100644 index 00000000..5dc6e6ba --- /dev/null +++ b/internal/vendored/go-tools/event/doc.go @@ -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 diff --git a/internal/vendored/go-tools/event/event.go b/internal/vendored/go-tools/event/event.go new file mode 100644 index 00000000..f48c8aba --- /dev/null +++ b/internal/vendored/go-tools/event/event.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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 +} diff --git a/internal/vendored/go-tools/event/export/eventtest/eventtest.go b/internal/vendored/go-tools/event/export/eventtest/eventtest.go new file mode 100644 index 00000000..0af6f4c7 --- /dev/null +++ b/internal/vendored/go-tools/event/export/eventtest/eventtest.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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 +} diff --git a/internal/vendored/go-tools/telemetry/export/id.go b/internal/vendored/go-tools/event/export/id.go similarity index 100% rename from internal/vendored/go-tools/telemetry/export/id.go rename to internal/vendored/go-tools/event/export/id.go diff --git a/internal/vendored/go-tools/telemetry/export/log.go b/internal/vendored/go-tools/event/export/log.go similarity index 65% rename from internal/vendored/go-tools/telemetry/export/log.go rename to internal/vendored/go-tools/event/export/log.go index 2c01b3ca..07885f27 100644 --- a/internal/vendored/go-tools/telemetry/export/log.go +++ b/internal/vendored/go-tools/event/export/log.go @@ -8,8 +8,11 @@ import ( "context" "fmt" "io" + "sync" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) // LogWriter returns an Exporter that logs events to the supplied writer. @@ -22,25 +25,30 @@ func LogWriter(w io.Writer, onlyErrors bool) event.Exporter { } type logWriter struct { + mu sync.Mutex + printer Printer writer io.Writer onlyErrors bool } -func (w *logWriter) ProcessEvent(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context { +func (w *logWriter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { switch { - case ev.IsLog(): - if w.onlyErrors && event.Err.Get(tagMap) == nil { + case event.IsLog(ev): + if w.onlyErrors && !event.IsError(ev) { return ctx } - fmt.Fprintf(w.writer, "%v\n", ev) - case ev.IsStartSpan(): + 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 ev.IsEndSpan(): + case event.IsEnd(ev): if span := GetSpan(ctx); span != nil { fmt.Fprintf(w.writer, "finish: %v %v", span.Name, span.ID) } diff --git a/internal/vendored/go-tools/event/export/log_test.go b/internal/vendored/go-tools/event/export/log_test.go new file mode 100644 index 00000000..66184f72 --- /dev/null +++ b/internal/vendored/go-tools/event/export/log_test.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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) + } +} diff --git a/internal/vendored/go-tools/telemetry/export/metric/data.go b/internal/vendored/go-tools/event/export/metric/data.go similarity index 64% rename from internal/vendored/go-tools/telemetry/export/metric/data.go rename to internal/vendored/go-tools/event/export/metric/data.go index 78e4ae2e..0b381ec0 100644 --- a/internal/vendored/go-tools/telemetry/export/metric/data.go +++ b/internal/vendored/go-tools/event/export/metric/data.go @@ -9,7 +9,8 @@ import ( "sort" "time" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) // Data represents a single point in the time series of a metric. @@ -22,7 +23,7 @@ type Data interface { //TODO: rethink the concept of metric handles Handle() string // Groups reports the rows that currently exist for this metric. - Groups() [][]event.Tag + Groups() [][]label.Label } // Int64Data is a concrete implementation of Data for int64 scalar metrics. @@ -36,8 +37,8 @@ type Int64Data struct { // End is the last time this metric was updated. EndTime time.Time - groups [][]event.Tag - key *event.Int64Key + groups [][]label.Label + key *keys.Int64 } // Float64Data is a concrete implementation of Data for float64 scalar metrics. @@ -51,8 +52,8 @@ type Float64Data struct { // End is the last time this metric was updated. EndTime time.Time - groups [][]event.Tag - key *event.Float64Key + groups [][]label.Label + key *keys.Float64 } // HistogramInt64Data is a concrete implementation of Data for int64 histogram metrics. @@ -64,8 +65,8 @@ type HistogramInt64Data struct { // End is the last time this metric was updated. EndTime time.Time - groups [][]event.Tag - key *event.Int64Key + groups [][]label.Label + key *keys.Int64 } // HistogramInt64Row holds the values for a single row of a HistogramInt64Data. @@ -91,8 +92,8 @@ type HistogramFloat64Data struct { // End is the last time this metric was updated. EndTime time.Time - groups [][]event.Tag - key *event.Float64Key + groups [][]label.Label + key *keys.Float64 } // HistogramFloat64Row holds the values for a single row of a HistogramFloat64Data. @@ -109,44 +110,44 @@ type HistogramFloat64Row struct { Max float64 } -func tagListEqual(a, b []event.Tag) bool { +func labelListEqual(a, b []label.Label) bool { //TODO: make this more efficient return fmt.Sprint(a) == fmt.Sprint(b) } -func tagListLess(a, b []event.Tag) bool { +func labelListLess(a, b []label.Label) bool { //TODO: make this more efficient return fmt.Sprint(a) < fmt.Sprint(b) } -func getGroup(tagMap event.TagMap, g *[][]event.Tag, keys []event.Key) (int, bool) { - group := make([]event.Tag, len(keys)) +func getGroup(lm label.Map, g *[][]label.Label, keys []label.Key) (int, bool) { + group := make([]label.Label, len(keys)) for i, key := range keys { - tag := tagMap.Find(key) - if tag.Valid() { - group[i] = tag + l := lm.Find(key) + if l.Valid() { + group[i] = l } } old := *g index := sort.Search(len(old), func(i int) bool { - return !tagListLess(old[i], group) + return !labelListLess(old[i], group) }) - if index < len(old) && tagListEqual(group, old[index]) { + if index < len(old) && labelListEqual(group, old[index]) { // not a new group return index, false } - *g = make([][]event.Tag, len(old)+1) + *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() [][]event.Tag { return data.groups } +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, tagMap event.TagMap, f func(v int64) int64) Data { - index, insert := getGroup(tagMap, &data.groups, data.Info.Keys) +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) @@ -162,29 +163,29 @@ func (data *Int64Data) modify(at time.Time, tagMap event.TagMap, f func(v int64) return &frozen } -func (data *Int64Data) count(at time.Time, tagMap event.TagMap, tag event.Tag) Data { - return data.modify(at, tagMap, func(v int64) int64 { +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, tagMap event.TagMap, tag event.Tag) Data { - return data.modify(at, tagMap, func(v int64) int64 { - return v + data.key.From(tag) +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, tagMap event.TagMap, tag event.Tag) Data { - return data.modify(at, tagMap, func(v int64) int64 { - return data.key.From(tag) +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() [][]event.Tag { return data.groups } +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, tagMap event.TagMap, f func(v float64) float64) Data { - index, insert := getGroup(tagMap, &data.groups, data.Info.Keys) +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) @@ -200,23 +201,23 @@ func (data *Float64Data) modify(at time.Time, tagMap event.TagMap, f func(v floa return &frozen } -func (data *Float64Data) sum(at time.Time, tagMap event.TagMap, tag event.Tag) Data { - return data.modify(at, tagMap, func(v float64) float64 { - return v + data.key.From(tag) +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, tagMap event.TagMap, tag event.Tag) Data { - return data.modify(at, tagMap, func(v float64) float64 { - return data.key.From(tag) +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() [][]event.Tag { return data.groups } +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, tagMap event.TagMap, f func(v *HistogramInt64Row)) Data { - index, insert := getGroup(tagMap, &data.groups, data.Info.Keys) +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 { @@ -238,9 +239,9 @@ func (data *HistogramInt64Data) modify(at time.Time, tagMap event.TagMap, f func return &frozen } -func (data *HistogramInt64Data) record(at time.Time, tagMap event.TagMap, tag event.Tag) Data { - return data.modify(at, tagMap, func(v *HistogramInt64Row) { - value := data.key.From(tag) +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 @@ -257,11 +258,11 @@ func (data *HistogramInt64Data) record(at time.Time, tagMap event.TagMap, tag ev }) } -func (data *HistogramFloat64Data) Handle() string { return data.Info.Name } -func (data *HistogramFloat64Data) Groups() [][]event.Tag { return data.groups } +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, tagMap event.TagMap, f func(v *HistogramFloat64Row)) Data { - index, insert := getGroup(tagMap, &data.groups, data.Info.Keys) +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 { @@ -283,9 +284,9 @@ func (data *HistogramFloat64Data) modify(at time.Time, tagMap event.TagMap, f fu return &frozen } -func (data *HistogramFloat64Data) record(at time.Time, tagMap event.TagMap, tag event.Tag) Data { - return data.modify(at, tagMap, func(v *HistogramFloat64Row) { - value := data.key.From(tag) +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 diff --git a/internal/vendored/go-tools/event/export/metric/exporter.go b/internal/vendored/go-tools/event/export/metric/exporter.go new file mode 100644 index 00000000..db16d147 --- /dev/null +++ b/internal/vendored/go-tools/event/export/metric/exporter.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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) + } +} diff --git a/internal/vendored/go-tools/telemetry/export/metric/info.go b/internal/vendored/go-tools/event/export/metric/info.go similarity index 78% rename from internal/vendored/go-tools/telemetry/export/metric/info.go rename to internal/vendored/go-tools/event/export/metric/info.go index f519d298..05c043e2 100644 --- a/internal/vendored/go-tools/telemetry/export/metric/info.go +++ b/internal/vendored/go-tools/event/export/metric/info.go @@ -5,7 +5,8 @@ package metric import ( - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) // Scalar represents the construction information for a scalar metric. @@ -14,8 +15,8 @@ type Scalar struct { Name string // Description can be used by observers to describe the metric to users. Description string - // Keys is the set of tags that collectively describe rows of the metric. - Keys []event.Key + // 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. @@ -24,8 +25,8 @@ type HistogramInt64 struct { Name string // Description can be used by observers to describe the metric to users. Description string - // Keys is the set of tags that collectively describe rows of the metric. - Keys []event.Key + // 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 } @@ -36,8 +37,8 @@ type HistogramFloat64 struct { Name string // Description can be used by observers to describe the metric to users. Description string - // Keys is the set of tags that collectively describe rows of the metric. - Keys []event.Key + // 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 } @@ -45,7 +46,7 @@ type HistogramFloat64 struct { // 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 event.Key) { +func (info Scalar) Count(e *Config, key label.Key) { data := &Int64Data{Info: &info, key: nil} e.subscribe(key, data.count) } @@ -53,7 +54,7 @@ func (info Scalar) Count(e *Config, key event.Key) { // 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 *event.Int64Key) { +func (info Scalar) SumInt64(e *Config, key *keys.Int64) { data := &Int64Data{Info: &info, key: key} e.subscribe(key, data.sum) } @@ -61,7 +62,7 @@ func (info Scalar) SumInt64(e *Config, key *event.Int64Key) { // 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 *event.Int64Key) { +func (info Scalar) LatestInt64(e *Config, key *keys.Int64) { data := &Int64Data{Info: &info, IsGauge: true, key: key} e.subscribe(key, data.latest) } @@ -69,7 +70,7 @@ func (info Scalar) LatestInt64(e *Config, key *event.Int64Key) { // 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 *event.Float64Key) { +func (info Scalar) SumFloat64(e *Config, key *keys.Float64) { data := &Float64Data{Info: &info, key: key} e.subscribe(key, data.sum) } @@ -77,7 +78,7 @@ func (info Scalar) SumFloat64(e *Config, key *event.Float64Key) { // 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 *event.Float64Key) { +func (info Scalar) LatestFloat64(e *Config, key *keys.Float64) { data := &Float64Data{Info: &info, IsGauge: true, key: key} e.subscribe(key, data.latest) } @@ -85,7 +86,7 @@ func (info Scalar) LatestFloat64(e *Config, key *event.Float64Key) { // 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 *event.Int64Key) { +func (info HistogramInt64) Record(e *Config, key *keys.Int64) { data := &HistogramInt64Data{Info: &info, key: key} e.subscribe(key, data.record) } @@ -93,7 +94,7 @@ func (info HistogramInt64) Record(e *Config, key *event.Int64Key) { // 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 *event.Float64Key) { +func (info HistogramFloat64) Record(e *Config, key *keys.Float64) { data := &HistogramFloat64Data{Info: &info, key: key} e.subscribe(key, data.record) } diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/README.md b/internal/vendored/go-tools/event/export/ocagent/README.md similarity index 97% rename from internal/vendored/go-tools/telemetry/export/ocagent/README.md rename to internal/vendored/go-tools/event/export/ocagent/README.md index 80fccee9..50df1154 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/README.md +++ b/internal/vendored/go-tools/event/export/ocagent/README.md @@ -37,10 +37,10 @@ import ( "net/http" "time" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/metric" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/ocagent" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/metric" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/ocagent" ) type testExporter struct { diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/metrics.go b/internal/vendored/go-tools/event/export/ocagent/metrics.go similarity index 96% rename from internal/vendored/go-tools/telemetry/export/ocagent/metrics.go rename to internal/vendored/go-tools/event/export/ocagent/metrics.go index b8b3450b..b0c50bf5 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/metrics.go +++ b/internal/vendored/go-tools/event/export/ocagent/metrics.go @@ -7,9 +7,9 @@ package ocagent import ( "time" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/metric" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/ocagent/wire" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/metric" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/ocagent/wire" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) // dataToMetricDescriptor return a *wire.MetricDescriptor based on data. @@ -201,7 +201,7 @@ func distributionToPoints(counts []int64, count int64, sum float64, bucketBounds // infoKeysToLabelKeys returns an array of *wire.LabelKeys containing the // string values of the elements of labelKeys. -func infoKeysToLabelKeys(infoKeys []event.Key) []*wire.LabelKey { +func infoKeysToLabelKeys(infoKeys []label.Key) []*wire.LabelKey { labelKeys := make([]*wire.LabelKey, 0, len(infoKeys)) for _, key := range infoKeys { labelKeys = append(labelKeys, &wire.LabelKey{ diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/metrics_test.go b/internal/vendored/go-tools/event/export/ocagent/metrics_test.go similarity index 89% rename from internal/vendored/go-tools/telemetry/export/ocagent/metrics_test.go rename to internal/vendored/go-tools/event/export/ocagent/metrics_test.go index 3d726ed5..15c6ddf7 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/metrics_test.go +++ b/internal/vendored/go-tools/event/export/ocagent/metrics_test.go @@ -5,7 +5,8 @@ import ( "errors" "testing" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" ) func TestEncodeMetric(t *testing.T) { @@ -22,9 +23,9 @@ func TestEncodeMetric(t *testing.T) { name: "HistogramFloat64, HistogramInt64", run: func(ctx context.Context) { ctx = event.Label(ctx, keyMethod.Of("godoc.ServeHTTP")) - event.Record(ctx, latencyMs.Of(96.58)) - ctx = event.Label(ctx, event.Err.Of(errors.New("panic: fatal signal"))) - event.Record(ctx, bytesIn.Of(97e2)) + 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 + ` { diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/ocagent.go b/internal/vendored/go-tools/event/export/ocagent/ocagent.go similarity index 59% rename from internal/vendored/go-tools/telemetry/export/ocagent/ocagent.go rename to internal/vendored/go-tools/event/export/ocagent/ocagent.go index 84e19edd..6c6c9287 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/ocagent.go +++ b/internal/vendored/go-tools/event/export/ocagent/ocagent.go @@ -18,10 +18,13 @@ import ( "sync" "time" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/metric" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/ocagent/wire" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/metric" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/ocagent/wire" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) type Config struct { @@ -34,6 +37,11 @@ type Config struct { 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 @@ -57,25 +65,33 @@ func Connect(config *Config) *Exporter { if config == nil || config.Address == "off" { return nil } - exporter := &Exporter{config: *config} - if exporter.config.Start.IsZero() { - exporter.config.Start = time.Now() - } - if exporter.config.Host == "" { + resolved := *config + if resolved.Host == "" { hostname, _ := os.Hostname() - exporter.config.Host = hostname + resolved.Host = hostname + } + if resolved.Process == 0 { + resolved.Process = uint32(os.Getpid()) } - if exporter.config.Process == 0 { - exporter.config.Process = uint32(os.Getpid()) + if resolved.Client == nil { + resolved.Client = http.DefaultClient } - if exporter.config.Client == nil { - exporter.config.Client = http.DefaultClient + if resolved.Service == "" { + resolved.Service = filepath.Base(os.Args[0]) } - if exporter.config.Service == "" { - exporter.config.Service = filepath.Base(os.Args[0]) + if resolved.Rate == 0 { + resolved.Rate = 2 * time.Second } - if exporter.config.Rate == 0 { - exporter.config.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) { @@ -85,19 +101,19 @@ func Connect(config *Config) *Exporter { return exporter } -func (e *Exporter) ProcessEvent(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context { +func (e *Exporter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { switch { - case ev.IsEndSpan(): + 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 ev.IsRecord(): + case event.IsMetric(ev): e.mu.Lock() defer e.mu.Unlock() - data := metric.Entries.Get(tagMap).([]metric.Data) + data := metric.Entries.Get(lm).([]metric.Data) e.metrics = append(e.metrics, data...) } return ctx @@ -198,9 +214,9 @@ func convertSpan(span *export.Span) *wire.Span { ParentSpanID: span.ParentID[:], Name: toTruncatableString(span.Name), Kind: wire.UnspecifiedSpanKind, - StartTime: convertTimestamp(span.Start().At), - EndTime: convertTimestamp(span.Finish().At), - Attributes: convertAttributes(event.Filter(span.Start().Tags(), event.Name)), + StartTime: convertTimestamp(span.Start().At()), + EndTime: convertTimestamp(span.Finish().At()), + Attributes: convertAttributes(span.Start(), 1), TimeEvents: convertEvents(span.Events()), SameProcessAsParentSpan: true, //TODO: StackTrace? @@ -227,58 +243,76 @@ func convertMetric(data metric.Data, start time.Time) *wire.Metric { } } -func convertAttributes(it event.TagIterator) *wire.Attributes { - if !it.Valid() { +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 ; it.Valid(); it.Advance() { - tag := it.Tag() - attributes[tag.Key.Name()] = convertAttribute(tag) + for { + if l.Valid() { + attributes[l.Key().Name()] = convertAttribute(l) + } + index++ + if !list.Valid(index) { + return &wire.Attributes{AttributeMap: attributes} + } + l = list.Label(index) } - return &wire.Attributes{AttributeMap: attributes} } -func convertAttribute(tag event.Tag) wire.Attribute { - switch key := tag.Key.(type) { - case *event.IntKey: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.Int8Key: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.Int16Key: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.Int32Key: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.Int64Key: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.UIntKey: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.UInt8Key: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.UInt16Key: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.UInt32Key: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.UInt64Key: - return wire.IntAttribute{IntValue: int64(key.From(tag))} - case *event.Float32Key: - return wire.DoubleAttribute{DoubleValue: float64(key.From(tag))} - case *event.Float64Key: - return wire.DoubleAttribute{DoubleValue: key.From(tag)} - case *event.BooleanKey: - return wire.BoolAttribute{BoolValue: key.From(tag)} - case *event.StringKey: - return wire.StringAttribute{StringValue: toTruncatableString(key.From(tag))} - case *event.ErrorKey: - return wire.StringAttribute{StringValue: toTruncatableString(key.From(tag).Error())} - case *event.ValueKey: - return wire.StringAttribute{StringValue: toTruncatableString(fmt.Sprint(key.From(tag)))} +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 []event.Event) *wire.TimeEvents { +func convertEvents(events []core.Event) *wire.TimeEvents { //TODO: MessageEvents? result := make([]wire.TimeEvent, len(events)) for i, event := range events { @@ -287,30 +321,38 @@ func convertEvents(events []event.Event) *wire.TimeEvents { return &wire.TimeEvents{TimeEvent: result} } -func convertEvent(ev event.Event) wire.TimeEvent { +func convertEvent(ev core.Event) wire.TimeEvent { return wire.TimeEvent{ - Time: convertTimestamp(ev.At), + Time: convertTimestamp(ev.At()), Annotation: convertAnnotation(ev), } } -func convertAnnotation(ev event.Event) *wire.Annotation { - tags := ev.Tags() - if !tags.Valid() { - return nil +func getAnnotationDescription(ev core.Event) (string, int) { + l := ev.Label(0) + if l.Key() != keys.Msg { + return "", 0 } - tagMap := ev.Map() - description := event.Msg.Get(tagMap) - tags = event.Filter(tags, event.Msg) - if description == "" { - err := event.Err.Get(tagMap) - tags = event.Filter(tags, event.Err) - if err != nil { - description = err.Error() - } + 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(tags), + Attributes: convertAttributes(ev, index), } } diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/ocagent_test.go b/internal/vendored/go-tools/event/export/ocagent/ocagent_test.go similarity index 61% rename from internal/vendored/go-tools/telemetry/export/ocagent/ocagent_test.go rename to internal/vendored/go-tools/event/export/ocagent/ocagent_test.go index 907c49b7..29fdf942 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/ocagent_test.go +++ b/internal/vendored/go-tools/event/export/ocagent/ocagent_test.go @@ -15,10 +15,13 @@ import ( "testing" "time" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/metric" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/ocagent" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/metric" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/ocagent" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) const testNodeStr = `{ @@ -39,53 +42,53 @@ const testNodeStr = `{ },` var ( - keyDB = event.NewStringKey("db", "the database name") - keyMethod = event.NewStringKey("method", "a metric grouping key") - keyRoute = event.NewStringKey("route", "another metric grouping key") + keyDB = keys.NewString("db", "the database name") + keyMethod = keys.NewString("method", "a metric grouping key") + keyRoute = keys.NewString("route", "another metric grouping key") - key1DB = event.NewStringKey("1_db", "A test string key") + key1DB = keys.NewString("1_db", "A test string key") - key2aAge = event.NewFloat64Key("2a_age", "A test float64 key") - key2bTTL = event.NewFloat32Key("2b_ttl", "A test float32 key") - key2cExpiryMS = event.NewFloat64Key("2c_expiry_ms", "A test float64 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 = event.NewBooleanKey("3a_retry", "A test boolean key") - key3bStale = event.NewBooleanKey("3b_stale", "Another test boolean key") + key3aRetry = keys.NewBoolean("3a_retry", "A test boolean key") + key3bStale = keys.NewBoolean("3b_stale", "Another test boolean key") - key4aMax = event.NewIntKey("4a_max", "A test int key") - key4bOpcode = event.NewInt8Key("4b_opcode", "A test int8 key") - key4cBase = event.NewInt16Key("4c_base", "A test int16 key") - key4eChecksum = event.NewInt32Key("4e_checksum", "A test int32 key") - key4fMode = event.NewInt64Key("4f_mode", "A test int64 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 = event.NewUIntKey("5a_min", "A test uint key") - key5bMix = event.NewUInt8Key("5b_mix", "A test uint8 key") - key5cPort = event.NewUInt16Key("5c_port", "A test uint16 key") - key5dMinHops = event.NewUInt32Key("5d_min_hops", "A test uint32 key") - key5eMaxHops = event.NewUInt64Key("5e_max_hops", "A test uint64 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 = event.NewInt64Key("recursive_calls", "Number of recursive calls") - bytesIn = event.NewInt64Key("bytes_in", "Number of bytes in") //, unit.Bytes) - latencyMs = event.NewFloat64Key("latency", "The latency in milliseconds") //, unit.Milliseconds) + 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: []event.Key{keyMethod, keyRoute}, + 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: []event.Key{keyMethod, keyRoute}, + 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: []event.Key{keyMethod, keyRoute}, + Keys: []label.Key{keyMethod, keyRoute}, } ) @@ -124,26 +127,26 @@ 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 event.Event, tagMap event.TagMap) context.Context { + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { switch { - case ev.IsStartSpan(): - ev.At = start - case ev.IsEndSpan(): - ev.At = end + case event.IsStart(ev): + ev = core.CloneEvent(ev, start) + case event.IsEnd(ev): + ev = core.CloneEvent(ev, end) default: - ev.At = at + ev = core.CloneEvent(ev, at) } - return output(ctx, ev, tagMap) + return output(ctx, ev, lm) } } func spanFixer(output event.Exporter) event.Exporter { - return func(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context { - if ev.IsStartSpan() { + 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, tagMap) + return output(ctx, ev, lm) } } diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/trace_test.go b/internal/vendored/go-tools/event/export/ocagent/trace_test.go similarity index 94% rename from internal/vendored/go-tools/telemetry/export/ocagent/trace_test.go rename to internal/vendored/go-tools/event/export/ocagent/trace_test.go index f82d6bae..4057b244 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/trace_test.go +++ b/internal/vendored/go-tools/event/export/ocagent/trace_test.go @@ -9,7 +9,7 @@ import ( "errors" "testing" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" ) func TestTrace(t *testing.T) { @@ -36,9 +36,9 @@ func TestTrace(t *testing.T) { want string }{ { - name: "no tags", + name: "no labels", run: func(ctx context.Context) { - event.Log(ctx) + event.Label(ctx) }, want: prefix + ` "timeEvent":[{"time":"1970-01-01T00:00:40Z"}] @@ -47,7 +47,7 @@ func TestTrace(t *testing.T) { { name: "description no error", run: func(ctx context.Context) { - event.Print(ctx, "cache miss", keyDB.Of("godb")) + event.Log(ctx, "cache miss", keyDB.Of("godb")) }, want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{ "description": { "value": "cache miss" }, @@ -97,7 +97,7 @@ func TestTrace(t *testing.T) { { name: "enumerate all attribute types", run: func(ctx context.Context) { - event.Print(ctx, "cache miss", + event.Log(ctx, "cache miss", key1DB.Of("godb"), key2aAge.Of(0.456), // Constant converted into "float64" @@ -148,7 +148,7 @@ func TestTrace(t *testing.T) { ctx := context.TODO() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx, done := event.StartSpan(ctx, "event span") + ctx, done := event.Start(ctx, "event span") tt.run(ctx) done() got := exporter.Output("/v1/trace") diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/wire/common.go b/internal/vendored/go-tools/event/export/ocagent/wire/common.go similarity index 93% rename from internal/vendored/go-tools/telemetry/export/ocagent/wire/common.go rename to internal/vendored/go-tools/event/export/ocagent/wire/common.go index 61dbfcd9..f22b5356 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/wire/common.go +++ b/internal/vendored/go-tools/event/export/ocagent/wire/common.go @@ -45,13 +45,13 @@ type DoubleAttribute struct { } type Attribute interface { - tagAttribute() + labelAttribute() } -func (StringAttribute) tagAttribute() {} -func (IntAttribute) tagAttribute() {} -func (BoolAttribute) tagAttribute() {} -func (DoubleAttribute) tagAttribute() {} +func (StringAttribute) labelAttribute() {} +func (IntAttribute) labelAttribute() {} +func (BoolAttribute) labelAttribute() {} +func (DoubleAttribute) labelAttribute() {} type StackTrace struct { StackFrames *StackFrames `json:"stack_frames,omitempty"` diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/wire/core.go b/internal/vendored/go-tools/event/export/ocagent/wire/core.go similarity index 100% rename from internal/vendored/go-tools/telemetry/export/ocagent/wire/core.go rename to internal/vendored/go-tools/event/export/ocagent/wire/core.go diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/wire/metrics.go b/internal/vendored/go-tools/event/export/ocagent/wire/metrics.go similarity index 94% rename from internal/vendored/go-tools/telemetry/export/ocagent/wire/metrics.go rename to internal/vendored/go-tools/event/export/ocagent/wire/metrics.go index 2a22da28..4cfdb88b 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/wire/metrics.go +++ b/internal/vendored/go-tools/event/export/ocagent/wire/metrics.go @@ -122,13 +122,13 @@ type PointSummaryValue struct { } type PointValue interface { - tagPointValue() + labelPointValue() } -func (PointInt64Value) tagPointValue() {} -func (PointDoubleValue) tagPointValue() {} -func (PointDistributionValue) tagPointValue() {} -func (PointSummaryValue) tagPointValue() {} +func (PointInt64Value) labelPointValue() {} +func (PointDoubleValue) labelPointValue() {} +func (PointDistributionValue) labelPointValue() {} +func (PointSummaryValue) labelPointValue() {} type DistributionValue struct { Count int64 `json:"count,omitempty"` @@ -143,15 +143,15 @@ type BucketOptionsExplicit struct { } type BucketOptions interface { - tagBucketOptions() + labelBucketOptions() } -func (*BucketOptionsExplicit) tagBucketOptions() {} +func (*BucketOptionsExplicit) labelBucketOptions() {} var _ BucketOptions = (*BucketOptionsExplicit)(nil) var _ json.Marshaler = (*BucketOptionsExplicit)(nil) -// Declared for the purpose of custom JSON marshalling without cycles. +// 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 diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/wire/metrics_test.go b/internal/vendored/go-tools/event/export/ocagent/wire/metrics_test.go similarity index 100% rename from internal/vendored/go-tools/telemetry/export/ocagent/wire/metrics_test.go rename to internal/vendored/go-tools/event/export/ocagent/wire/metrics_test.go diff --git a/internal/vendored/go-tools/telemetry/export/ocagent/wire/trace.go b/internal/vendored/go-tools/event/export/ocagent/wire/trace.go similarity index 96% rename from internal/vendored/go-tools/telemetry/export/ocagent/wire/trace.go rename to internal/vendored/go-tools/event/export/ocagent/wire/trace.go index c1a79a58..88856673 100644 --- a/internal/vendored/go-tools/telemetry/export/ocagent/wire/trace.go +++ b/internal/vendored/go-tools/event/export/ocagent/wire/trace.go @@ -79,11 +79,11 @@ const ( ) type TimeEventValue interface { - tagTimeEventValue() + labelTimeEventValue() } -func (Annotation) tagTimeEventValue() {} -func (MessageEvent) tagTimeEventValue() {} +func (Annotation) labelTimeEventValue() {} +func (MessageEvent) labelTimeEventValue() {} type Links struct { Link []*Link `json:"link,omitempty"` diff --git a/internal/vendored/go-tools/event/export/printer.go b/internal/vendored/go-tools/event/export/printer.go new file mode 100644 index 00000000..e45c6c7e --- /dev/null +++ b/internal/vendored/go-tools/event/export/printer.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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") +} diff --git a/internal/vendored/go-tools/telemetry/export/prometheus/prometheus.go b/internal/vendored/go-tools/event/export/prometheus/prometheus.go similarity index 87% rename from internal/vendored/go-tools/telemetry/export/prometheus/prometheus.go rename to internal/vendored/go-tools/event/export/prometheus/prometheus.go index 911827cd..0eb72652 100644 --- a/internal/vendored/go-tools/telemetry/export/prometheus/prometheus.go +++ b/internal/vendored/go-tools/event/export/prometheus/prometheus.go @@ -12,8 +12,10 @@ import ( "sort" "sync" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/export/metric" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/metric" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) func New() *Exporter { @@ -25,13 +27,13 @@ type Exporter struct { metrics []metric.Data } -func (e *Exporter) ProcessEvent(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context { - if !ev.IsRecord() { +func (e *Exporter) ProcessEvent(ctx context.Context, ev core.Event, ln label.Map) context.Context { + if !event.IsMetric(ev) { return ctx } e.mu.Lock() defer e.mu.Unlock() - metrics := metric.Entries.Get(tagMap).([]metric.Data) + metrics := metric.Entries.Get(ln).([]metric.Data) for _, data := range metrics { name := data.Handle() // We keep the metrics in name sorted order so the page is stable and easy @@ -64,7 +66,7 @@ func (e *Exporter) header(w http.ResponseWriter, name, description string, isGau fmt.Fprintf(w, "# TYPE %s %s\n", name, kind) } -func (e *Exporter) row(w http.ResponseWriter, name string, group []event.Tag, extra string, value interface{}) { +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) diff --git a/internal/vendored/go-tools/event/export/tag.go b/internal/vendored/go-tools/event/export/tag.go new file mode 100644 index 00000000..b6ec2c5f --- /dev/null +++ b/internal/vendored/go-tools/event/export/tag.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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) + } +} diff --git a/internal/vendored/go-tools/telemetry/export/trace.go b/internal/vendored/go-tools/event/export/trace.go similarity index 69% rename from internal/vendored/go-tools/telemetry/export/trace.go rename to internal/vendored/go-tools/event/export/trace.go index 9de5a4a7..2fc25710 100644 --- a/internal/vendored/go-tools/telemetry/export/trace.go +++ b/internal/vendored/go-tools/event/export/trace.go @@ -9,7 +9,10 @@ import ( "fmt" "sync" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" ) type SpanContext struct { @@ -22,9 +25,9 @@ type Span struct { ID SpanContext ParentID SpanID mu sync.Mutex - start event.Event - finish event.Event - events []event.Event + start core.Event + finish core.Event + events []core.Event } type contextKeyType int @@ -44,21 +47,21 @@ func GetSpan(ctx context.Context) *Span { // Spans creates an exporter that maintains hierarchical span structure in the // context. -// It creates new spans on EventStartSpan, adds events to the current span on -// EventLog or EventTag, and closes the span on EventEndSpan. +// 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 event.Event, tagMap event.TagMap) context.Context { + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { switch { - case ev.IsLog(), ev.IsLabel(): + 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 ev.IsStartSpan(): + case event.IsStart(ev): span := &Span{ - Name: event.Name.Get(tagMap), + Name: keys.Start.Get(lm), start: ev, } if parent := GetSpan(ctx); parent != nil { @@ -69,16 +72,16 @@ func Spans(output event.Exporter) event.Exporter { } span.ID.SpanID = newSpanID() ctx = context.WithValue(ctx, spanContextKey, span) - case ev.IsEndSpan(): + case event.IsEnd(ev): if span := GetSpan(ctx); span != nil { span.mu.Lock() span.finish = ev span.mu.Unlock() } - case ev.IsDetach(): + case event.IsDetach(ev): ctx = context.WithValue(ctx, spanContextKey, nil) } - return output(ctx, ev, tagMap) + return output(ctx, ev, lm) } } @@ -86,18 +89,18 @@ func (s *SpanContext) Format(f fmt.State, r rune) { fmt.Fprintf(f, "%v:%v", s.TraceID, s.SpanID) } -func (s *Span) Start() event.Event { +func (s *Span) Start() core.Event { // start never changes after construction, so we dont need to hold the mutex return s.start } -func (s *Span) Finish() event.Event { +func (s *Span) Finish() core.Event { s.mu.Lock() defer s.mu.Unlock() return s.finish } -func (s *Span) Events() []event.Event { +func (s *Span) Events() []core.Event { s.mu.Lock() defer s.mu.Unlock() return s.events diff --git a/internal/vendored/go-tools/event/keys/keys.go b/internal/vendored/go-tools/event/keys/keys.go new file mode 100644 index 00000000..5ab5cb14 --- /dev/null +++ b/internal/vendored/go-tools/event/keys/keys.go @@ -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" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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 +} diff --git a/internal/vendored/go-tools/event/keys/standard.go b/internal/vendored/go-tools/event/keys/standard.go new file mode 100644 index 00000000..7e958665 --- /dev/null +++ b/internal/vendored/go-tools/event/keys/standard.go @@ -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") +) diff --git a/internal/vendored/go-tools/event/label/label.go b/internal/vendored/go-tools/event/label/label.go new file mode 100644 index 00000000..b55c12eb --- /dev/null +++ b/internal/vendored/go-tools/event/label/label.go @@ -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 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 } + +// 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: unsafe.Pointer(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.(unsafe.Pointer)) + hdr.Len = int(t.packed) + return *(*string)(unsafe.Pointer(hdr)) +} + +// 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} +} diff --git a/internal/vendored/go-tools/event/label/label_test.go b/internal/vendored/go-tools/event/label/label_test.go new file mode 100644 index 00000000..54d599ae --- /dev/null +++ b/internal/vendored/go-tools/event/label/label_test.go @@ -0,0 +1,269 @@ +// 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" + "testing" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/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() +} diff --git a/internal/vendored/go-tools/fakenet/conn.go b/internal/vendored/go-tools/fakenet/conn.go new file mode 100644 index 00000000..c9cdaf27 --- /dev/null +++ b/internal/vendored/go-tools/fakenet/conn.go @@ -0,0 +1,129 @@ +// Copyright 2018 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 fakenet + +import ( + "io" + "net" + "sync" + "time" +) + +// NewConn returns a net.Conn built on top of the supplied reader and writer. +// It decouples the read and write on the conn from the underlying stream +// to enable Close to abort ones that are in progress. +// It's primary use is to fake a network connection from stdin and stdout. +func NewConn(name string, in io.ReadCloser, out io.WriteCloser) net.Conn { + c := &fakeConn{ + name: name, + reader: newFeeder(in.Read), + writer: newFeeder(out.Write), + in: in, + out: out, + } + go c.reader.run() + go c.writer.run() + return c +} + +type fakeConn struct { + name string + reader *connFeeder + writer *connFeeder + in io.ReadCloser + out io.WriteCloser +} + +type fakeAddr string + +// connFeeder serializes calls to the source function (io.Reader.Read or +// io.Writer.Write) by delegating them to a channel. This also allows calls to +// be intercepted when the connection is closed, and cancelled early if the +// connection is closed while the calls are still outstanding. +type connFeeder struct { + source func([]byte) (int, error) + input chan []byte + result chan feedResult + mu sync.Mutex + closed bool + done chan struct{} +} + +type feedResult struct { + n int + err error +} + +func (c *fakeConn) Close() error { + c.reader.close() + c.writer.close() + c.in.Close() + c.out.Close() + return nil +} + +func (c *fakeConn) Read(b []byte) (n int, err error) { return c.reader.do(b) } +func (c *fakeConn) Write(b []byte) (n int, err error) { return c.writer.do(b) } +func (c *fakeConn) LocalAddr() net.Addr { return fakeAddr(c.name) } +func (c *fakeConn) RemoteAddr() net.Addr { return fakeAddr(c.name) } +func (c *fakeConn) SetDeadline(t time.Time) error { return nil } +func (c *fakeConn) SetReadDeadline(t time.Time) error { return nil } +func (c *fakeConn) SetWriteDeadline(t time.Time) error { return nil } +func (a fakeAddr) Network() string { return "fake" } +func (a fakeAddr) String() string { return string(a) } + +func newFeeder(source func([]byte) (int, error)) *connFeeder { + return &connFeeder{ + source: source, + input: make(chan []byte), + result: make(chan feedResult), + done: make(chan struct{}), + } +} + +func (f *connFeeder) close() { + f.mu.Lock() + if !f.closed { + f.closed = true + close(f.done) + } + f.mu.Unlock() +} + +func (f *connFeeder) do(b []byte) (n int, err error) { + // send the request to the worker + select { + case f.input <- b: + case <-f.done: + return 0, io.EOF + } + // get the result from the worker + select { + case r := <-f.result: + return r.n, r.err + case <-f.done: + return 0, io.EOF + } +} + +func (f *connFeeder) run() { + var b []byte + for { + // wait for an input request + select { + case b = <-f.input: + case <-f.done: + return + } + // invoke the underlying method + n, err := f.source(b) + // send the result back to the requester + select { + case f.result <- feedResult{n: n, err: err}: + case <-f.done: + return + } + } +} diff --git a/internal/vendored/go-tools/jsonrpc2/conn.go b/internal/vendored/go-tools/jsonrpc2/conn.go new file mode 100644 index 00000000..27a47b14 --- /dev/null +++ b/internal/vendored/go-tools/jsonrpc2/conn.go @@ -0,0 +1,262 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "context" + "encoding/json" + "fmt" + "sync" + "sync/atomic" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/lsp/debug/tag" +) + +// Conn is the common interface to jsonrpc clients and servers. +// Conn is bidirectional; it does not have a designated server or client end. +// It manages the jsonrpc2 protocol, connecting responses back to their calls. +type Conn interface { + // Call invokes the target method and waits for a response. + // The params will be marshaled to JSON before sending over the wire, and will + // be handed to the method invoked. + // The response will be unmarshaled from JSON into the result. + // The id returned will be unique from this connection, and can be used for + // logging or tracking. + Call(ctx context.Context, method string, params, result interface{}) (ID, error) + + // Notify invokes the target method but does not wait for a response. + // The params will be marshaled to JSON before sending over the wire, and will + // be handed to the method invoked. + Notify(ctx context.Context, method string, params interface{}) error + + // Go starts a goroutine to handle the connection. + // It must be called exactly once for each Conn. + // It returns immediately. + // You must block on Done() to wait for the connection to shut down. + // This is a temporary measure, this should be started automatically in the + // future. + Go(ctx context.Context, handler Handler) + + // Close closes the connection and it's underlying stream. + // It does not wait for the close to complete, use the Done() channel for + // that. + Close() error + + // Done returns a channel that will be closed when the processing goroutine + // has terminated, which will happen if Close() is called or an underlying + // stream is closed. + Done() <-chan struct{} + + // Err returns an error if there was one from within the processing goroutine. + // If err returns non nil, the connection will be already closed or closing. + Err() error +} + +type conn struct { + seq int64 // must only be accessed using atomic operations + writeMu sync.Mutex // protects writes to the stream + stream Stream + pendingMu sync.Mutex // protects the pending map + pending map[ID]chan *Response + + done chan struct{} + err atomic.Value +} + +// NewConn creates a new connection object around the supplied stream. +func NewConn(s Stream) Conn { + conn := &conn{ + stream: s, + pending: make(map[ID]chan *Response), + done: make(chan struct{}), + } + return conn +} + +func (c *conn) Notify(ctx context.Context, method string, params interface{}) (err error) { + notify, err := NewNotification(method, params) + if err != nil { + return fmt.Errorf("marshaling notify parameters: %v", err) + } + ctx, done := event.Start(ctx, method, + tag.Method.Of(method), + tag.RPCDirection.Of(tag.Outbound), + ) + defer func() { + recordStatus(ctx, err) + done() + }() + + event.Metric(ctx, tag.Started.Of(1)) + n, err := c.write(ctx, notify) + event.Metric(ctx, tag.SentBytes.Of(n)) + return err +} + +func (c *conn) Call(ctx context.Context, method string, params, result interface{}) (_ ID, err error) { + // generate a new request identifier + id := ID{number: atomic.AddInt64(&c.seq, 1)} + call, err := NewCall(id, method, params) + if err != nil { + return id, fmt.Errorf("marshaling call parameters: %v", err) + } + ctx, done := event.Start(ctx, method, + tag.Method.Of(method), + tag.RPCDirection.Of(tag.Outbound), + tag.RPCID.Of(fmt.Sprintf("%q", id)), + ) + defer func() { + recordStatus(ctx, err) + done() + }() + event.Metric(ctx, tag.Started.Of(1)) + // We have to add ourselves to the pending map before we send, otherwise we + // are racing the response. Also add a buffer to rchan, so that if we get a + // wire response between the time this call is cancelled and id is deleted + // from c.pending, the send to rchan will not block. + rchan := make(chan *Response, 1) + c.pendingMu.Lock() + c.pending[id] = rchan + c.pendingMu.Unlock() + defer func() { + c.pendingMu.Lock() + delete(c.pending, id) + c.pendingMu.Unlock() + }() + // now we are ready to send + n, err := c.write(ctx, call) + event.Metric(ctx, tag.SentBytes.Of(n)) + if err != nil { + // sending failed, we will never get a response, so don't leave it pending + return id, err + } + // now wait for the response + select { + case response := <-rchan: + // is it an error response? + if response.err != nil { + return id, response.err + } + if result == nil || len(response.result) == 0 { + return id, nil + } + if err := json.Unmarshal(response.result, result); err != nil { + return id, fmt.Errorf("unmarshaling result: %v", err) + } + return id, nil + case <-ctx.Done(): + return id, ctx.Err() + } +} + +func (c *conn) replier(req Request, spanDone func()) Replier { + return func(ctx context.Context, result interface{}, err error) error { + defer func() { + recordStatus(ctx, err) + spanDone() + }() + call, ok := req.(*Call) + if !ok { + // request was a notify, no need to respond + return nil + } + response, err := NewResponse(call.id, result, err) + if err != nil { + return err + } + n, err := c.write(ctx, response) + event.Metric(ctx, tag.SentBytes.Of(n)) + if err != nil { + // TODO(iancottrell): if a stream write fails, we really need to shut down + // the whole stream + return err + } + return nil + } +} + +func (c *conn) write(ctx context.Context, msg Message) (int64, error) { + c.writeMu.Lock() + defer c.writeMu.Unlock() + return c.stream.Write(ctx, msg) +} + +func (c *conn) Go(ctx context.Context, handler Handler) { + go c.run(ctx, handler) +} + +func (c *conn) run(ctx context.Context, handler Handler) { + defer close(c.done) + for { + // get the next message + msg, n, err := c.stream.Read(ctx) + if err != nil { + // The stream failed, we cannot continue. + c.fail(err) + return + } + switch msg := msg.(type) { + case Request: + labels := []label.Label{ + tag.Method.Of(msg.Method()), + tag.RPCDirection.Of(tag.Inbound), + {}, // reserved for ID if present + } + if call, ok := msg.(*Call); ok { + labels[len(labels)-1] = tag.RPCID.Of(fmt.Sprintf("%q", call.ID())) + } else { + labels = labels[:len(labels)-1] + } + reqCtx, spanDone := event.Start(ctx, msg.Method(), labels...) + event.Metric(reqCtx, + tag.Started.Of(1), + tag.ReceivedBytes.Of(n)) + if err := handler(reqCtx, c.replier(msg, spanDone), msg); err != nil { + // delivery failed, not much we can do + event.Error(reqCtx, "jsonrpc2 message delivery failed", err) + } + case *Response: + // If method is not set, this should be a response, in which case we must + // have an id to send the response back to the caller. + c.pendingMu.Lock() + rchan, ok := c.pending[msg.id] + c.pendingMu.Unlock() + if ok { + rchan <- msg + } + } + } +} + +func (c *conn) Close() error { + return c.stream.Close() +} + +func (c *conn) Done() <-chan struct{} { + return c.done +} + +func (c *conn) Err() error { + if err := c.err.Load(); err != nil { + return err.(error) + } + return nil +} + +// fail sets a failure condition on the stream and closes it. +func (c *conn) fail(err error) { + c.err.Store(err) + c.stream.Close() +} + +func recordStatus(ctx context.Context, err error) { + if err != nil { + event.Label(ctx, tag.StatusCode.Of("ERROR")) + } else { + event.Label(ctx, tag.StatusCode.Of("OK")) + } +} diff --git a/internal/vendored/go-tools/jsonrpc2/handler.go b/internal/vendored/go-tools/jsonrpc2/handler.go index 83515790..8a3aea7c 100644 --- a/internal/vendored/go-tools/jsonrpc2/handler.go +++ b/internal/vendored/go-tools/jsonrpc2/handler.go @@ -6,80 +6,104 @@ package jsonrpc2 import ( "context" -) - -// Handler is the interface used to hook into the message handling of an rpc -// connection. -type Handler interface { - // Deliver is invoked to handle incoming requests. - // If the request returns false from IsNotify then the Handler must eventually - // call Reply on the Conn with the supplied request. - // Handlers are called synchronously, they should pass the work off to a go - // routine if they are going to take a long time. - // If Deliver returns true all subsequent handlers will be invoked with - // delivered set to true, and should not attempt to deliver the message. - Deliver(ctx context.Context, r *Request, delivered bool) bool - - // Cancel is invoked for cancelled outgoing requests. - // It is okay to use the connection to send notifications, but the context will - // be in the cancelled state, so you must do it with the background context - // instead. - // If Cancel returns true all subsequent handlers will be invoked with - // cancelled set to true, and should not attempt to cancel the message. - Cancel(ctx context.Context, conn *Conn, id ID, cancelled bool) bool - - // Request is called near the start of processing any request. - Request(ctx context.Context, conn *Conn, direction Direction, r *WireRequest) context.Context -} - -// Direction is used to indicate to a logger whether the logged message was being -// sent or received. -type Direction bool + "fmt" + "sync" -const ( - // Send indicates the message is outgoing. - Send = Direction(true) - // Receive indicates the message is incoming. - Receive = Direction(false) + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" ) -func (d Direction) String() string { - switch d { - case Send: - return "send" - case Receive: - return "receive" - default: - panic("unreachable") - } -} +// Handler is invoked to handle incoming requests. +// The Replier sends a reply to the request and must be called exactly once. +type Handler func(ctx context.Context, reply Replier, req Request) error -type EmptyHandler struct{} +// Replier is passed to handlers to allow them to reply to the request. +// If err is set then result will be ignored. +type Replier func(ctx context.Context, result interface{}, err error) error -func (EmptyHandler) Deliver(ctx context.Context, r *Request, delivered bool) bool { - return false +// MethodNotFound is a Handler that replies to all call requests with the +// standard method not found response. +// This should normally be the final handler in a chain. +func MethodNotFound(ctx context.Context, reply Replier, req Request) error { + return reply(ctx, nil, fmt.Errorf("%w: %q", ErrMethodNotFound, req.Method())) } -func (EmptyHandler) Cancel(ctx context.Context, conn *Conn, id ID, cancelled bool) bool { - return false -} - -func (EmptyHandler) Request(ctx context.Context, conn *Conn, direction Direction, r *WireRequest) context.Context { - return ctx +// MustReplyHandler creates a Handler that panics if the wrapped handler does +// not call Reply for every request that it is passed. +func MustReplyHandler(handler Handler) Handler { + return func(ctx context.Context, reply Replier, req Request) error { + called := false + err := handler(ctx, func(ctx context.Context, result interface{}, err error) error { + if called { + panic(fmt.Errorf("request %q replied to more than once", req.Method())) + } + called = true + return reply(ctx, result, err) + }, req) + if !called { + panic(fmt.Errorf("request %q was never replied to", req.Method())) + } + return err + } } -func (EmptyHandler) Response(ctx context.Context, conn *Conn, direction Direction, r *WireResponse) context.Context { - return ctx +// CancelHandler returns a handler that supports cancellation, and a function +// that can be used to trigger canceling in progress requests. +func CancelHandler(handler Handler) (Handler, func(id ID)) { + var mu sync.Mutex + handling := make(map[ID]context.CancelFunc) + wrapped := func(ctx context.Context, reply Replier, req Request) error { + if call, ok := req.(*Call); ok { + cancelCtx, cancel := context.WithCancel(ctx) + ctx = cancelCtx + mu.Lock() + handling[call.ID()] = cancel + mu.Unlock() + innerReply := reply + reply = func(ctx context.Context, result interface{}, err error) error { + mu.Lock() + delete(handling, call.ID()) + mu.Unlock() + return innerReply(ctx, result, err) + } + } + return handler(ctx, reply, req) + } + return wrapped, func(id ID) { + mu.Lock() + cancel, found := handling[id] + mu.Unlock() + if found { + cancel() + } + } } -type defaultHandler struct{ EmptyHandler } - -func (defaultHandler) Deliver(ctx context.Context, r *Request, delivered bool) bool { - if delivered { - return false - } - if !r.IsNotify() { - r.Reply(ctx, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method)) +// AsyncHandler returns a handler that processes each request goes in its own +// goroutine. +// The handler returns immediately, without the request being processed. +// Each request then waits for the previous request to finish before it starts. +// This allows the stream to unblock at the cost of unbounded goroutines +// all stalled on the previous one. +func AsyncHandler(handler Handler) Handler { + nextRequest := make(chan struct{}) + close(nextRequest) + return func(ctx context.Context, reply Replier, req Request) error { + waitForPrevious := nextRequest + nextRequest = make(chan struct{}) + unlockNext := nextRequest + innerReply := reply + reply = func(ctx context.Context, result interface{}, err error) error { + close(unlockNext) + return innerReply(ctx, result, err) + } + _, queueDone := event.Start(ctx, "queued") + go func() { + <-waitForPrevious + queueDone() + if err := handler(ctx, reply, req); err != nil { + event.Error(ctx, "jsonrpc2 async message delivery failed", err) + } + }() + return nil } - return true } diff --git a/internal/vendored/go-tools/jsonrpc2/jsonrpc2.go b/internal/vendored/go-tools/jsonrpc2/jsonrpc2.go index 4864b094..5a529950 100644 --- a/internal/vendored/go-tools/jsonrpc2/jsonrpc2.go +++ b/internal/vendored/go-tools/jsonrpc2/jsonrpc2.go @@ -7,413 +7,11 @@ // It is intended to be compatible with other implementations at the wire level. package jsonrpc2 -import ( - "context" - "encoding/json" - "fmt" - "sync" - "sync/atomic" - - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/lsp/debug/tag" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" -) - -// Conn is a JSON RPC 2 client server connection. -// Conn is bidirectional; it does not have a designated server or client end. -type Conn struct { - seq int64 // must only be accessed using atomic operations - handlers []Handler - stream Stream - pendingMu sync.Mutex // protects the pending map - pending map[ID]chan *WireResponse - handlingMu sync.Mutex // protects the handling map - handling map[ID]*Request -} - -type requestState int - const ( - requestWaiting = requestState(iota) - requestSerial - requestParallel - requestReplied - requestDone + // ErrIdleTimeout is returned when serving timed out waiting for new connections. + ErrIdleTimeout = constError("timed out waiting for new connections") ) -// Request is sent to a server to represent a Call or Notify operaton. -type Request struct { - conn *Conn - cancel context.CancelFunc - state requestState - nextRequest chan struct{} - - // The Wire values of the request. - WireRequest -} - -// NewErrorf builds a Error struct for the supplied message and code. -// If args is not empty, message and args will be passed to Sprintf. -func NewErrorf(code int64, format string, args ...interface{}) *Error { - return &Error{ - Code: code, - Message: fmt.Sprintf(format, args...), - } -} - -// NewConn creates a new connection object around the supplied stream. -// You must call Run for the connection to be active. -func NewConn(s Stream) *Conn { - conn := &Conn{ - handlers: []Handler{defaultHandler{}}, - stream: s, - pending: make(map[ID]chan *WireResponse), - handling: make(map[ID]*Request), - } - return conn -} - -// AddHandler adds a new handler to the set the connection will invoke. -// Handlers are invoked in the reverse order of how they were added, this -// allows the most recent addition to be the first one to attempt to handle a -// message. -func (c *Conn) AddHandler(handler Handler) { - // prepend the new handlers so we use them first - c.handlers = append([]Handler{handler}, c.handlers...) -} - -// Cancel cancels a pending Call on the server side. -// The call is identified by its id. -// JSON RPC 2 does not specify a cancel message, so cancellation support is not -// directly wired in. This method allows a higher level protocol to choose how -// to propagate the cancel. -func (c *Conn) Cancel(id ID) { - c.handlingMu.Lock() - handling, found := c.handling[id] - c.handlingMu.Unlock() - if found { - handling.cancel() - } -} - -// Notify is called to send a notification request over the connection. -// It will return as soon as the notification has been sent, as no response is -// possible. -func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (err error) { - jsonParams, err := marshalToRaw(params) - if err != nil { - return fmt.Errorf("marshalling notify parameters: %v", err) - } - request := &WireRequest{ - Method: method, - Params: jsonParams, - } - data, err := json.Marshal(request) - if err != nil { - return fmt.Errorf("marshalling notify request: %v", err) - } - for _, h := range c.handlers { - ctx = h.Request(ctx, c, Send, request) - } - ctx, done := event.StartSpan(ctx, request.Method, - tag.Method.Of(request.Method), - tag.RPCDirection.Of(tag.Outbound), - tag.RPCID.Of(request.ID.String()), - ) - defer func() { - recordStatus(ctx, err) - done() - }() - - event.Record(ctx, tag.Started.Of(1)) - n, err := c.stream.Write(ctx, data) - event.Record(ctx, tag.ReceivedBytes.Of(n)) - return err -} - -// Call sends a request over the connection and then waits for a response. -// If the response is not an error, it will be decoded into result. -// result must be of a type you an pass to json.Unmarshal. -func (c *Conn) Call(ctx context.Context, method string, params, result interface{}) (err error) { - // generate a new request identifier - id := ID{Number: atomic.AddInt64(&c.seq, 1)} - jsonParams, err := marshalToRaw(params) - if err != nil { - return fmt.Errorf("marshalling call parameters: %v", err) - } - request := &WireRequest{ - ID: &id, - Method: method, - Params: jsonParams, - } - // marshal the request now it is complete - data, err := json.Marshal(request) - if err != nil { - return fmt.Errorf("marshalling call request: %v", err) - } - for _, h := range c.handlers { - ctx = h.Request(ctx, c, Send, request) - } - ctx, done := event.StartSpan(ctx, request.Method, - tag.Method.Of(request.Method), - tag.RPCDirection.Of(tag.Outbound), - tag.RPCID.Of(request.ID.String()), - ) - defer func() { - recordStatus(ctx, err) - done() - }() - event.Record(ctx, tag.Started.Of(1)) - // We have to add ourselves to the pending map before we send, otherwise we - // are racing the response. Also add a buffer to rchan, so that if we get a - // wire response between the time this call is cancelled and id is deleted - // from c.pending, the send to rchan will not block. - rchan := make(chan *WireResponse, 1) - c.pendingMu.Lock() - c.pending[id] = rchan - c.pendingMu.Unlock() - defer func() { - c.pendingMu.Lock() - delete(c.pending, id) - c.pendingMu.Unlock() - }() - // now we are ready to send - n, err := c.stream.Write(ctx, data) - event.Record(ctx, tag.ReceivedBytes.Of(n)) - if err != nil { - // sending failed, we will never get a response, so don't leave it pending - return err - } - // now wait for the response - select { - case response := <-rchan: - // is it an error response? - if response.Error != nil { - return response.Error - } - if result == nil || response.Result == nil { - return nil - } - if err := json.Unmarshal(*response.Result, result); err != nil { - return fmt.Errorf("unmarshalling result: %v", err) - } - return nil - case <-ctx.Done(): - // Allow the handler to propagate the cancel. - cancelled := false - for _, h := range c.handlers { - if h.Cancel(ctx, c, id, cancelled) { - cancelled = true - } - } - return ctx.Err() - } -} - -// Conn returns the connection that created this request. -func (r *Request) Conn() *Conn { return r.conn } - -// IsNotify returns true if this request is a notification. -func (r *Request) IsNotify() bool { - return r.ID == nil -} - -// Parallel indicates that the system is now allowed to process other requests -// in parallel with this one. -// It is safe to call any number of times, but must only be called from the -// request handling go routine. -// It is implied by both reply and by the handler returning. -func (r *Request) Parallel() { - if r.state >= requestParallel { - return - } - r.state = requestParallel - close(r.nextRequest) -} - -// Reply sends a reply to the given request. -// It is an error to call this if request was not a call. -// You must call this exactly once for any given request. -// It should only be called from the handler go routine. -// If err is set then result will be ignored. -// If the request has not yet dropped into parallel mode -// it will be before this function returns. -func (r *Request) Reply(ctx context.Context, result interface{}, err error) error { - if r.state >= requestReplied { - return fmt.Errorf("reply invoked more than once") - } - if r.IsNotify() { - return fmt.Errorf("reply not invoked with a valid call: %v, %v", r.Method, r.Params) - } - // reply ends the handling phase of a call, so if we are not yet - // parallel we should be now. The go routine is allowed to continue - // to do work after replying, which is why it is important to unlock - // the rpc system at this point. - r.Parallel() - r.state = requestReplied - - var raw *json.RawMessage - if err == nil { - raw, err = marshalToRaw(result) - } - response := &WireResponse{ - Result: raw, - ID: r.ID, - } - if err != nil { - if callErr, ok := err.(*Error); ok { - response.Error = callErr - } else { - response.Error = NewErrorf(0, "%s", err) - } - } - data, err := json.Marshal(response) - if err != nil { - return err - } - n, err := r.conn.stream.Write(ctx, data) - event.Record(ctx, tag.ReceivedBytes.Of(n)) - - if err != nil { - // TODO(iancottrell): if a stream write fails, we really need to shut down - // the whole stream - return err - } - return nil -} - -func (c *Conn) setHandling(r *Request, active bool) { - if r.ID == nil { - return - } - r.conn.handlingMu.Lock() - defer r.conn.handlingMu.Unlock() - if active { - r.conn.handling[*r.ID] = r - } else { - delete(r.conn.handling, *r.ID) - } -} - -// combined has all the fields of both Request and Response. -// We can decode this and then work out which it is. -type combined struct { - VersionTag VersionTag `json:"jsonrpc"` - ID *ID `json:"id,omitempty"` - Method string `json:"method"` - Params *json.RawMessage `json:"params,omitempty"` - Result *json.RawMessage `json:"result,omitempty"` - Error *Error `json:"error,omitempty"` -} - -// Run blocks until the connection is terminated, and returns any error that -// caused the termination. -// It must be called exactly once for each Conn. -// It returns only when the reader is closed or there is an error in the stream. -func (c *Conn) Run(runCtx context.Context) error { - // we need to make the next request "lock" in an unlocked state to allow - // the first incoming request to proceed. All later requests are unlocked - // by the preceding request going to parallel mode. - nextRequest := make(chan struct{}) - close(nextRequest) - for { - // get the data for a message - data, n, err := c.stream.Read(runCtx) - if err != nil { - // The stream failed, we cannot continue. If the client disconnected - // normally, we should get ErrDisconnected here. - return err - } - // read a combined message - msg := &combined{} - if err := json.Unmarshal(data, msg); err != nil { - // a badly formed message arrived, log it and continue - // we trust the stream to have isolated the error to just this message - continue - } - // Work out whether this is a request or response. - switch { - case msg.Method != "": - // If method is set it must be a request. - reqCtx, cancelReq := context.WithCancel(runCtx) - thisRequest := nextRequest - nextRequest = make(chan struct{}) - req := &Request{ - conn: c, - cancel: cancelReq, - nextRequest: nextRequest, - WireRequest: WireRequest{ - VersionTag: msg.VersionTag, - Method: msg.Method, - Params: msg.Params, - ID: msg.ID, - }, - } - for _, h := range c.handlers { - reqCtx = h.Request(reqCtx, c, Receive, &req.WireRequest) - } - reqCtx, done := event.StartSpan(reqCtx, req.WireRequest.Method, - tag.Method.Of(req.WireRequest.Method), - tag.RPCDirection.Of(tag.Inbound), - tag.RPCID.Of(req.WireRequest.ID.String()), - ) - event.Record(reqCtx, - tag.Started.Of(1), - tag.SentBytes.Of(n)) - c.setHandling(req, true) - _, queueDone := event.StartSpan(reqCtx, "queued") - go func() { - <-thisRequest - queueDone() - req.state = requestSerial - defer func() { - c.setHandling(req, false) - if !req.IsNotify() && req.state < requestReplied { - req.Reply(reqCtx, nil, NewErrorf(CodeInternalError, "method %q did not reply", req.Method)) - } - req.Parallel() - recordStatus(reqCtx, nil) - done() - cancelReq() - }() - delivered := false - for _, h := range c.handlers { - if h.Deliver(reqCtx, req, delivered) { - delivered = true - } - } - }() - case msg.ID != nil: - // If method is not set, this should be a response, in which case we must - // have an id to send the response back to the caller. - c.pendingMu.Lock() - rchan, ok := c.pending[*msg.ID] - c.pendingMu.Unlock() - if ok { - response := &WireResponse{ - Result: msg.Result, - Error: msg.Error, - ID: msg.ID, - } - rchan <- response - } - default: - } - } -} - -func marshalToRaw(obj interface{}) (*json.RawMessage, error) { - data, err := json.Marshal(obj) - if err != nil { - return nil, err - } - raw := json.RawMessage(data) - return &raw, nil -} +type constError string -func recordStatus(ctx context.Context, err error) { - if err != nil { - event.Label(ctx, tag.StatusCode.Of("ERROR")) - } else { - event.Label(ctx, tag.StatusCode.Of("OK")) - } -} +func (e constError) Error() string { return string(e) } diff --git a/internal/vendored/go-tools/jsonrpc2/jsonrpc2_test.go b/internal/vendored/go-tools/jsonrpc2/jsonrpc2_test.go index 546c936c..16b19835 100644 --- a/internal/vendored/go-tools/jsonrpc2/jsonrpc2_test.go +++ b/internal/vendored/go-tools/jsonrpc2/jsonrpc2_test.go @@ -9,14 +9,14 @@ import ( "encoding/json" "flag" "fmt" - "io" - "log" + "net" "path" "reflect" "testing" - "time" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export/eventtest" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/stack/stacktest" ) var logRPC = flag.Bool("logrpc", false, "Enable jsonrpc2 communication logging") @@ -60,143 +60,89 @@ func (test *callTest) verifyResults(t *testing.T, results interface{}) { } } -func TestPlainCall(t *testing.T) { - ctx := context.Background() - a, b := prepare(ctx, t, false) - for _, test := range callTests { - results := test.newResults() - if err := a.Call(ctx, test.method, test.params, results); err != nil { - t.Fatalf("%v:Call failed: %v", test.method, err) +func TestCall(t *testing.T) { + stacktest.NoLeak(t) + ctx := eventtest.NewContext(context.Background(), t) + for _, headers := range []bool{false, true} { + name := "Plain" + if headers { + name = "Headers" } - test.verifyResults(t, results) - if err := b.Call(ctx, test.method, test.params, results); err != nil { - t.Fatalf("%v:Call failed: %v", test.method, err) - } - test.verifyResults(t, results) + t.Run(name, func(t *testing.T) { + ctx := eventtest.NewContext(ctx, t) + a, b, done := prepare(ctx, t, headers) + defer done() + for _, test := range callTests { + t.Run(test.method, func(t *testing.T) { + ctx := eventtest.NewContext(ctx, t) + results := test.newResults() + if _, err := a.Call(ctx, test.method, test.params, results); err != nil { + t.Fatalf("%v:Call failed: %v", test.method, err) + } + test.verifyResults(t, results) + if _, err := b.Call(ctx, test.method, test.params, results); err != nil { + t.Fatalf("%v:Call failed: %v", test.method, err) + } + test.verifyResults(t, results) + }) + } + }) } } -func TestHeaderCall(t *testing.T) { - ctx := context.Background() - a, b := prepare(ctx, t, true) - for _, test := range callTests { - results := test.newResults() - if err := a.Call(ctx, test.method, test.params, results); err != nil { - t.Fatalf("%v:Call failed: %v", test.method, err) - } - test.verifyResults(t, results) - if err := b.Call(ctx, test.method, test.params, results); err != nil { - t.Fatalf("%v:Call failed: %v", test.method, err) - } - test.verifyResults(t, results) +func prepare(ctx context.Context, t *testing.T, withHeaders bool) (jsonrpc2.Conn, jsonrpc2.Conn, func()) { + // make a wait group that can be used to wait for the system to shut down + aPipe, bPipe := net.Pipe() + a := run(ctx, withHeaders, aPipe) + b := run(ctx, withHeaders, bPipe) + return a, b, func() { + a.Close() + b.Close() + <-a.Done() + <-b.Done() } } -func prepare(ctx context.Context, t *testing.T, withHeaders bool) (*jsonrpc2.Conn, *jsonrpc2.Conn) { - aR, bW := io.Pipe() - bR, aW := io.Pipe() - a := run(ctx, t, withHeaders, aR, aW) - b := run(ctx, t, withHeaders, bR, bW) - return a, b -} - -func run(ctx context.Context, t *testing.T, withHeaders bool, r io.ReadCloser, w io.WriteCloser) *jsonrpc2.Conn { +func run(ctx context.Context, withHeaders bool, nc net.Conn) jsonrpc2.Conn { var stream jsonrpc2.Stream if withHeaders { - stream = jsonrpc2.NewHeaderStream(r, w) + stream = jsonrpc2.NewHeaderStream(nc) } else { - stream = jsonrpc2.NewStream(r, w) + stream = jsonrpc2.NewRawStream(nc) } conn := jsonrpc2.NewConn(stream) - conn.AddHandler(&handle{log: *logRPC}) - go func() { - defer func() { - r.Close() - w.Close() - }() - if err := conn.Run(ctx); err != nil { - t.Errorf("Stream failed: %v", err) - } - }() + conn.Go(ctx, testHandler(*logRPC)) return conn } -type handle struct { - log bool -} - -func (h *handle) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool { - switch r.Method { - case "no_args": - if r.Params != nil { - r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) - return true +func testHandler(log bool) jsonrpc2.Handler { + return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + switch req.Method() { + case "no_args": + if len(req.Params()) > 0 { + return reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)) + } + return reply(ctx, true, nil) + case "one_string": + var v string + if err := json.Unmarshal(req.Params(), &v); err != nil { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) + } + return reply(ctx, "got:"+v, nil) + case "one_number": + var v int + if err := json.Unmarshal(req.Params(), &v); err != nil { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) + } + return reply(ctx, fmt.Sprintf("got:%d", v), nil) + case "join": + var v []string + if err := json.Unmarshal(req.Params(), &v); err != nil { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) + } + return reply(ctx, path.Join(v...), nil) + default: + return jsonrpc2.MethodNotFound(ctx, reply, req) } - r.Reply(ctx, true, nil) - case "one_string": - var v string - if err := json.Unmarshal(*r.Params, &v); err != nil { - r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())) - return true - } - r.Reply(ctx, "got:"+v, nil) - case "one_number": - var v int - if err := json.Unmarshal(*r.Params, &v); err != nil { - r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())) - return true - } - r.Reply(ctx, fmt.Sprintf("got:%d", v), nil) - case "join": - var v []string - if err := json.Unmarshal(*r.Params, &v); err != nil { - r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())) - return true - } - r.Reply(ctx, path.Join(v...), nil) - default: - r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method)) } - return true -} - -func (h *handle) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool { - return false -} - -func (h *handle) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context { - if h.log { - if r.ID != nil { - log.Printf("%v call [%v] %s %v", direction, r.ID, r.Method, r.Params) - } else { - log.Printf("%v notification %s %v", direction, r.Method, r.Params) - } - ctx = context.WithValue(ctx, "method", r.Method) - ctx = context.WithValue(ctx, "start", time.Now()) - } - return ctx -} - -func (h *handle) Response(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireResponse) context.Context { - if h.log { - method := ctx.Value("method") - elapsed := time.Since(ctx.Value("start").(time.Time)) - log.Printf("%v response in %v [%v] %s %v", direction, elapsed, r.ID, method, r.Result) - } - return ctx -} - -func (h *handle) Done(ctx context.Context, err error) { -} - -func (h *handle) Read(ctx context.Context, bytes int64) context.Context { - return ctx -} - -func (h *handle) Wrote(ctx context.Context, bytes int64) context.Context { - return ctx -} - -func (h *handle) Error(ctx context.Context, err error) { - log.Printf("%v", err) } diff --git a/internal/vendored/go-tools/jsonrpc2/messages.go b/internal/vendored/go-tools/jsonrpc2/messages.go new file mode 100644 index 00000000..58d285d9 --- /dev/null +++ b/internal/vendored/go-tools/jsonrpc2/messages.go @@ -0,0 +1,238 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "encoding/json" + "errors" + "fmt" +) + +// Message is the interface to all jsonrpc2 message types. +// They share no common functionality, but are a closed set of concrete types +// that are allowed to implement this interface. The message types are *Call, +// *Notification and *Response. +type Message interface { + // isJSONRPC2Message is used to make the set of message implementations a + // closed set. + isJSONRPC2Message() +} + +// Request is the shared interface to jsonrpc2 messages that request +// a method be invoked. +// The request types are a closed set of *Call and *Notification. +type Request interface { + Message + // Method is a string containing the method name to invoke. + Method() string + // Params is either a struct or an array with the parameters of the method. + Params() json.RawMessage + // isJSONRPC2Request is used to make the set of request implementations closed. + isJSONRPC2Request() +} + +// Notification is a request for which a response cannot occur, and as such +// it has not ID. +type Notification struct { + // Method is a string containing the method name to invoke. + method string + params json.RawMessage +} + +// Call is a request that expects a response. +// The response will have a matching ID. +type Call struct { + // Method is a string containing the method name to invoke. + method string + // Params is either a struct or an array with the parameters of the method. + params json.RawMessage + // id of this request, used to tie the Response back to the request. + id ID +} + +// Response is a reply to a Call. +// It will have the same ID as the call it is a response to. +type Response struct { + // result is the content of the response. + result json.RawMessage + // err is set only if the call failed. + err error + // ID of the request this is a response to. + id ID +} + +// NewNotification constructs a new Notification message for the supplied +// method and parameters. +func NewNotification(method string, params interface{}) (*Notification, error) { + p, merr := marshalToRaw(params) + return &Notification{method: method, params: p}, merr +} + +func (msg *Notification) Method() string { return msg.method } +func (msg *Notification) Params() json.RawMessage { return msg.params } +func (msg *Notification) isJSONRPC2Message() {} +func (msg *Notification) isJSONRPC2Request() {} + +func (n *Notification) MarshalJSON() ([]byte, error) { + msg := wireRequest{Method: n.method, Params: &n.params} + data, err := json.Marshal(msg) + if err != nil { + return data, fmt.Errorf("marshaling notification: %w", err) + } + return data, nil +} + +func (n *Notification) UnmarshalJSON(data []byte) error { + msg := wireRequest{} + if err := json.Unmarshal(data, &msg); err != nil { + return fmt.Errorf("unmarshaling notification: %w", err) + } + n.method = msg.Method + if msg.Params != nil { + n.params = *msg.Params + } + return nil +} + +// NewCall constructs a new Call message for the supplied ID, method and +// parameters. +func NewCall(id ID, method string, params interface{}) (*Call, error) { + p, merr := marshalToRaw(params) + return &Call{id: id, method: method, params: p}, merr +} + +func (msg *Call) Method() string { return msg.method } +func (msg *Call) Params() json.RawMessage { return msg.params } +func (msg *Call) ID() ID { return msg.id } +func (msg *Call) isJSONRPC2Message() {} +func (msg *Call) isJSONRPC2Request() {} + +func (c *Call) MarshalJSON() ([]byte, error) { + msg := wireRequest{Method: c.method, Params: &c.params, ID: &c.id} + data, err := json.Marshal(msg) + if err != nil { + return data, fmt.Errorf("marshaling call: %w", err) + } + return data, nil +} + +func (c *Call) UnmarshalJSON(data []byte) error { + msg := wireRequest{} + if err := json.Unmarshal(data, &msg); err != nil { + return fmt.Errorf("unmarshaling call: %w", err) + } + c.method = msg.Method + if msg.Params != nil { + c.params = *msg.Params + } + if msg.ID != nil { + c.id = *msg.ID + } + return nil +} + +// NewResponse constructs a new Response message that is a reply to the +// supplied. If err is set result may be ignored. +func NewResponse(id ID, result interface{}, err error) (*Response, error) { + r, merr := marshalToRaw(result) + return &Response{id: id, result: r, err: err}, merr +} + +func (msg *Response) ID() ID { return msg.id } +func (msg *Response) Result() json.RawMessage { return msg.result } +func (msg *Response) Err() error { return msg.err } +func (msg *Response) isJSONRPC2Message() {} + +func (r *Response) MarshalJSON() ([]byte, error) { + msg := &wireResponse{Error: toWireError(r.err), ID: &r.id} + if msg.Error == nil { + msg.Result = &r.result + } + data, err := json.Marshal(msg) + if err != nil { + return data, fmt.Errorf("marshaling notification: %w", err) + } + return data, nil +} + +func toWireError(err error) *wireError { + if err == nil { + // no error, the response is complete + return nil + } + if err, ok := err.(*wireError); ok { + // already a wire error, just use it + return err + } + result := &wireError{Message: err.Error()} + var wrapped *wireError + if errors.As(err, &wrapped) { + // if we wrapped a wire error, keep the code from the wrapped error + // but the message from the outer error + result.Code = wrapped.Code + } + return result +} + +func (r *Response) UnmarshalJSON(data []byte) error { + msg := wireResponse{} + if err := json.Unmarshal(data, &msg); err != nil { + return fmt.Errorf("unmarshaling jsonrpc response: %w", err) + } + if msg.Result != nil { + r.result = *msg.Result + } + if msg.Error != nil { + r.err = msg.Error + } + if msg.ID != nil { + r.id = *msg.ID + } + return nil +} + +func DecodeMessage(data []byte) (Message, error) { + msg := wireCombined{} + if err := json.Unmarshal(data, &msg); err != nil { + return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err) + } + if msg.Method == "" { + // no method, should be a response + if msg.ID == nil { + return nil, ErrInvalidRequest + } + response := &Response{id: *msg.ID} + if msg.Error != nil { + response.err = msg.Error + } + if msg.Result != nil { + response.result = *msg.Result + } + return response, nil + } + // has a method, must be a request + if msg.ID == nil { + // request with no ID is a notify + notify := &Notification{method: msg.Method} + if msg.Params != nil { + notify.params = *msg.Params + } + return notify, nil + } + // request with an ID, must be a call + call := &Call{method: msg.Method, id: *msg.ID} + if msg.Params != nil { + call.params = *msg.Params + } + return call, nil +} + +func marshalToRaw(obj interface{}) (json.RawMessage, error) { + data, err := json.Marshal(obj) + if err != nil { + return json.RawMessage{}, err + } + return json.RawMessage(data), nil +} diff --git a/internal/vendored/go-tools/jsonrpc2/serve.go b/internal/vendored/go-tools/jsonrpc2/serve.go index c2cfa603..91aff057 100644 --- a/internal/vendored/go-tools/jsonrpc2/serve.go +++ b/internal/vendored/go-tools/jsonrpc2/serve.go @@ -8,10 +8,12 @@ import ( "context" "errors" "fmt" - "log" + "io" "net" "os" "time" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" ) // NOTE: This file provides an experimental API for serving multiple remote @@ -20,27 +22,27 @@ import ( // semantics. // A StreamServer is used to serve incoming jsonrpc2 clients communicating over -// a newly created stream. +// a newly created connection. type StreamServer interface { - ServeStream(context.Context, Stream) error + ServeStream(context.Context, Conn) error } // The ServerFunc type is an adapter that implements the StreamServer interface // using an ordinary function. -type ServerFunc func(context.Context, Stream) error +type ServerFunc func(context.Context, Conn) error // ServeStream calls f(ctx, s). -func (f ServerFunc) ServeStream(ctx context.Context, s Stream) error { - return f(ctx, s) +func (f ServerFunc) ServeStream(ctx context.Context, c Conn) error { + return f(ctx, c) } // HandlerServer returns a StreamServer that handles incoming streams using the // provided handler. func HandlerServer(h Handler) StreamServer { - return ServerFunc(func(ctx context.Context, s Stream) error { - conn := NewConn(s) - conn.AddHandler(h) - return conn.Run(ctx) + return ServerFunc(func(ctx context.Context, conn Conn) error { + conn.Go(ctx, h) + <-conn.Done() + return conn.Err() }) } @@ -59,13 +61,12 @@ func ListenAndServe(ctx context.Context, network, addr string, server StreamServ return Serve(ctx, ln, server, idleTimeout) } -// ErrIdleTimeout is returned when serving timed out waiting for new connections. -var ErrIdleTimeout = errors.New("timed out waiting for new connections") - // Serve accepts incoming connections from the network, and handles them using // the provided server. If idleTimeout is non-zero, ListenAndServe exits after // there are no clients for this duration, otherwise it exits only on error. func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeout time.Duration) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Max duration: ~290 years; surely that's long enough. const forever = 1<<63 - 1 if idleTimeout <= 0 { @@ -81,7 +82,10 @@ func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeou for { nc, err := ln.Accept() if err != nil { - doneListening <- fmt.Errorf("Accept(): %v", err) + select { + case doneListening <- fmt.Errorf("Accept(): %w", err): + case <-ctx.Done(): + } return } newConns <- nc @@ -94,14 +98,18 @@ func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeou case netConn := <-newConns: activeConns++ connTimer.Stop() - stream := NewHeaderStream(netConn, netConn) + stream := NewHeaderStream(netConn) go func() { - closedConns <- server.ServeStream(ctx, stream) + conn := NewConn(stream) + closedConns <- server.ServeStream(ctx, conn) + stream.Close() }() case err := <-doneListening: return err case err := <-closedConns: - log.Printf("closed a connection with error: %v", err) + if !isClosingError(err) { + event.Error(ctx, "closed a connection", err) + } activeConns-- if activeConns == 0 { connTimer.Reset(idleTimeout) @@ -113,3 +121,19 @@ func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeou } } } + +// isClosingError reports if the error occurs normally during the process of +// closing a network connection. It uses imperfect heuristics that err on the +// side of false negatives, and should not be used for anything critical. +func isClosingError(err error) bool { + if errors.Is(err, io.EOF) { + return true + } + // Per https://github.com/golang/go/issues/4373, this error string should not + // change. This is not ideal, but since the worst that could happen here is + // some superfluous logging, it is acceptable. + if err.Error() == "use of closed network connection" { + return true + } + return false +} diff --git a/internal/vendored/go-tools/jsonrpc2/serve_test.go b/internal/vendored/go-tools/jsonrpc2/serve_test.go index ec6e1e12..baacadf2 100644 --- a/internal/vendored/go-tools/jsonrpc2/serve_test.go +++ b/internal/vendored/go-tools/jsonrpc2/serve_test.go @@ -10,13 +10,16 @@ import ( "sync" "testing" "time" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/stack/stacktest" ) func TestIdleTimeout(t *testing.T) { + stacktest.NoLeak(t) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - ln, err := net.Listen("tcp", ":0") + ln, err := net.Listen("tcp", "localhost:0") if err != nil { t.Fatal(err) } @@ -30,7 +33,7 @@ func TestIdleTimeout(t *testing.T) { return conn } - server := HandlerServer(EmptyHandler{}) + server := HandlerServer(MethodNotFound) // connTimer := &fakeTimer{c: make(chan time.Time, 1)} var ( runErr error diff --git a/internal/vendored/go-tools/jsonrpc2/servertest/servertest.go b/internal/vendored/go-tools/jsonrpc2/servertest/servertest.go index 981ecac2..f4caa5e6 100644 --- a/internal/vendored/go-tools/jsonrpc2/servertest/servertest.go +++ b/internal/vendored/go-tools/jsonrpc2/servertest/servertest.go @@ -9,8 +9,8 @@ package servertest import ( "context" "fmt" - "io" "net" + "strings" "sync" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" @@ -18,109 +18,102 @@ import ( // Connector is the interface used to connect to a server. type Connector interface { - Connect(context.Context) *jsonrpc2.Conn + Connect(context.Context) jsonrpc2.Conn } // TCPServer is a helper for executing tests against a remote jsonrpc2 // connection. Once initialized, its Addr field may be used to connect a // jsonrpc2 client. type TCPServer struct { + *connList + Addr string - ln net.Listener - cls *closerList + ln net.Listener + framer jsonrpc2.Framer } // NewTCPServer returns a new test server listening on local tcp port and // serving incoming jsonrpc2 streams using the provided stream server. It // panics on any error. -func NewTCPServer(ctx context.Context, server jsonrpc2.StreamServer) *TCPServer { +func NewTCPServer(ctx context.Context, server jsonrpc2.StreamServer, framer jsonrpc2.Framer) *TCPServer { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { panic(fmt.Sprintf("servertest: failed to listen: %v", err)) } + if framer == nil { + framer = jsonrpc2.NewHeaderStream + } go jsonrpc2.Serve(ctx, ln, server, 0) - return &TCPServer{Addr: ln.Addr().String(), ln: ln, cls: &closerList{}} + return &TCPServer{Addr: ln.Addr().String(), ln: ln, framer: framer, connList: &connList{}} } // Connect dials the test server and returns a jsonrpc2 Connection that is // ready for use. -func (s *TCPServer) Connect(ctx context.Context) *jsonrpc2.Conn { +func (s *TCPServer) Connect(ctx context.Context) jsonrpc2.Conn { netConn, err := net.Dial("tcp", s.Addr) if err != nil { panic(fmt.Sprintf("servertest: failed to connect to test instance: %v", err)) } - s.cls.add(func() { - netConn.Close() - }) - conn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn, netConn)) - go conn.Run(ctx) + conn := jsonrpc2.NewConn(s.framer(netConn)) + s.add(conn) return conn } -// Close closes all connected pipes. -func (s *TCPServer) Close() error { - s.cls.closeAll() - return nil -} - // PipeServer is a test server that handles connections over io.Pipes. type PipeServer struct { + *connList server jsonrpc2.StreamServer - cls *closerList + framer jsonrpc2.Framer } // NewPipeServer returns a test server that can be connected to via io.Pipes. -func NewPipeServer(ctx context.Context, server jsonrpc2.StreamServer) *PipeServer { - return &PipeServer{server: server, cls: &closerList{}} +func NewPipeServer(ctx context.Context, server jsonrpc2.StreamServer, framer jsonrpc2.Framer) *PipeServer { + if framer == nil { + framer = jsonrpc2.NewRawStream + } + return &PipeServer{server: server, framer: framer, connList: &connList{}} } // Connect creates new io.Pipes and binds them to the underlying StreamServer. -func (s *PipeServer) Connect(ctx context.Context) *jsonrpc2.Conn { - // Pipes connect like this: - // Client🡒(sWriter)🡒(sReader)🡒Server - // 🡔(cReader)🡐(cWriter)🡗 - sReader, sWriter := io.Pipe() - cReader, cWriter := io.Pipe() - s.cls.add(func() { - sReader.Close() - sWriter.Close() - cReader.Close() - cWriter.Close() - }) - serverStream := jsonrpc2.NewStream(sReader, cWriter) - go s.server.ServeStream(ctx, serverStream) - - clientStream := jsonrpc2.NewStream(cReader, sWriter) +func (s *PipeServer) Connect(ctx context.Context) jsonrpc2.Conn { + sPipe, cPipe := net.Pipe() + serverStream := s.framer(sPipe) + serverConn := jsonrpc2.NewConn(serverStream) + s.add(serverConn) + go s.server.ServeStream(ctx, serverConn) + + clientStream := s.framer(cPipe) clientConn := jsonrpc2.NewConn(clientStream) - go clientConn.Run(ctx) + s.add(clientConn) return clientConn } -// Close closes all connected pipes. -func (s *PipeServer) Close() error { - s.cls.closeAll() - return nil -} - -// closerList tracks closers to run when a testserver is closed. This is a +// connList tracks closers to run when a testserver is closed. This is a // convenience, so that callers don't have to worry about closing each // connection. -type closerList struct { - mu sync.Mutex - closers []func() +type connList struct { + mu sync.Mutex + conns []jsonrpc2.Conn } -func (l *closerList) add(closer func()) { +func (l *connList) add(conn jsonrpc2.Conn) { l.mu.Lock() defer l.mu.Unlock() - l.closers = append(l.closers, closer) + l.conns = append(l.conns, conn) } -func (l *closerList) closeAll() { +func (l *connList) Close() error { l.mu.Lock() defer l.mu.Unlock() - for _, closer := range l.closers { - closer() + var errmsgs []string + for _, conn := range l.conns { + if err := conn.Close(); err != nil { + errmsgs = append(errmsgs, err.Error()) + } } + if len(errmsgs) > 0 { + return fmt.Errorf("closing errors:\n%s", strings.Join(errmsgs, "\n")) + } + return nil } diff --git a/internal/vendored/go-tools/jsonrpc2/servertest/servertest_test.go b/internal/vendored/go-tools/jsonrpc2/servertest/servertest_test.go index 81d302e9..07393870 100644 --- a/internal/vendored/go-tools/jsonrpc2/servertest/servertest_test.go +++ b/internal/vendored/go-tools/jsonrpc2/servertest/servertest_test.go @@ -12,28 +12,21 @@ import ( "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" ) -type fakeHandler struct { - jsonrpc2.EmptyHandler -} - type msg struct { Msg string } -func (fakeHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool { - if err := r.Reply(ctx, &msg{"pong"}, nil); err != nil { - panic(err) - } - return true +func fakeHandler(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + return reply(ctx, &msg{"pong"}, nil) } func TestTestServer(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - server := jsonrpc2.HandlerServer(fakeHandler{}) - tcpTS := NewTCPServer(ctx, server) + server := jsonrpc2.HandlerServer(fakeHandler) + tcpTS := NewTCPServer(ctx, server, nil) defer tcpTS.Close() - pipeTS := NewPipeServer(ctx, server) + pipeTS := NewPipeServer(ctx, server, nil) defer pipeTS.Close() tests := []struct { @@ -47,8 +40,9 @@ func TestTestServer(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { conn := test.connector.Connect(ctx) + conn.Go(ctx, jsonrpc2.MethodNotFound) var got msg - if err := conn.Call(ctx, "ping", &msg{"ping"}, &got); err != nil { + if _, err := conn.Call(ctx, "ping", &msg{"ping"}, &got); err != nil { t.Fatal(err) } if want := "pong"; got.Msg != want { diff --git a/internal/vendored/go-tools/jsonrpc2/stream.go b/internal/vendored/go-tools/jsonrpc2/stream.go index 2d1e7c44..b22be7ee 100644 --- a/internal/vendored/go-tools/jsonrpc2/stream.go +++ b/internal/vendored/go-tools/jsonrpc2/stream.go @@ -8,47 +8,50 @@ import ( "bufio" "context" "encoding/json" - "errors" "fmt" "io" + "net" "strconv" "strings" - "sync" ) // Stream abstracts the transport mechanics from the JSON RPC protocol. // A Conn reads and writes messages using the stream it was provided on // construction, and assumes that each call to Read or Write fully transfers // a single message, or returns an error. +// A stream is not safe for concurrent use, it is expected it will be used by +// a single Conn in a safe manner. type Stream interface { // Read gets the next message from the stream. - // It is never called concurrently. - Read(context.Context) ([]byte, int64, error) + Read(context.Context) (Message, int64, error) // Write sends a message to the stream. - // It must be safe for concurrent use. - Write(context.Context, []byte) (int64, error) + Write(context.Context, Message) (int64, error) + // Close closes the connection. + // Any blocked Read or Write operations will be unblocked and return errors. + Close() error } -// ErrDisconnected signals that the stream or connection exited normally. -var ErrDisconnected = errors.New("disconnected") +// Framer wraps a network connection up into a Stream. +// It is responsible for the framing and encoding of messages into wire form. +// NewRawStream and NewHeaderStream are implementations of a Framer. +type Framer func(conn net.Conn) Stream -// NewStream returns a Stream built on top of an io.Reader and io.Writer +// NewRawStream returns a Stream built on top of a net.Conn. // The messages are sent with no wrapping, and rely on json decode consistency // to determine message boundaries. -func NewStream(in io.Reader, out io.Writer) Stream { - return &plainStream{ - in: json.NewDecoder(in), - out: out, +func NewRawStream(conn net.Conn) Stream { + return &rawStream{ + conn: conn, + in: json.NewDecoder(conn), } } -type plainStream struct { - in *json.Decoder - outMu sync.Mutex - out io.Writer +type rawStream struct { + conn net.Conn + in *json.Decoder } -func (s *plainStream) Read(ctx context.Context) ([]byte, int64, error) { +func (s *rawStream) Read(ctx context.Context) (Message, int64, error) { select { case <-ctx.Done(): return nil, 0, ctx.Err() @@ -56,43 +59,46 @@ func (s *plainStream) Read(ctx context.Context) ([]byte, int64, error) { } var raw json.RawMessage if err := s.in.Decode(&raw); err != nil { - if err == io.EOF { - return nil, 0, ErrDisconnected - } return nil, 0, err } - return raw, int64(len(raw)), nil + msg, err := DecodeMessage(raw) + return msg, int64(len(raw)), err } -func (s *plainStream) Write(ctx context.Context, data []byte) (int64, error) { +func (s *rawStream) Write(ctx context.Context, msg Message) (int64, error) { select { case <-ctx.Done(): return 0, ctx.Err() default: } - s.outMu.Lock() - n, err := s.out.Write(data) - s.outMu.Unlock() + data, err := json.Marshal(msg) + if err != nil { + return 0, fmt.Errorf("marshaling message: %v", err) + } + n, err := s.conn.Write(data) return int64(n), err } -// NewHeaderStream returns a Stream built on top of an io.Reader and io.Writer +func (s *rawStream) Close() error { + return s.conn.Close() +} + +// NewHeaderStream returns a Stream built on top of a net.Conn. // The messages are sent with HTTP content length and MIME type headers. // This is the format used by LSP and others. -func NewHeaderStream(in io.Reader, out io.Writer) Stream { +func NewHeaderStream(conn net.Conn) Stream { return &headerStream{ - in: bufio.NewReader(in), - out: out, + conn: conn, + in: bufio.NewReader(conn), } } type headerStream struct { - in *bufio.Reader - outMu sync.Mutex - out io.Writer + conn net.Conn + in *bufio.Reader } -func (s *headerStream) Read(ctx context.Context) ([]byte, int64, error) { +func (s *headerStream) Read(ctx context.Context) (Message, int64, error) { select { case <-ctx.Done(): return nil, 0, ctx.Err() @@ -103,12 +109,8 @@ func (s *headerStream) Read(ctx context.Context) ([]byte, int64, error) { for { line, err := s.in.ReadString('\n') total += int64(len(line)) - if err == io.EOF { - // A normal disconnection will terminate with EOF before the next header. - return nil, total, ErrDisconnected - } if err != nil { - return nil, total, fmt.Errorf("failed reading header line %q", err) + return nil, total, fmt.Errorf("failed reading header line: %w", err) } line = strings.TrimSpace(line) // check we have a header line @@ -140,22 +142,29 @@ func (s *headerStream) Read(ctx context.Context) ([]byte, int64, error) { return nil, total, err } total += length - return data, total, nil + msg, err := DecodeMessage(data) + return msg, total, err } -func (s *headerStream) Write(ctx context.Context, data []byte) (int64, error) { +func (s *headerStream) Write(ctx context.Context, msg Message) (int64, error) { select { case <-ctx.Done(): return 0, ctx.Err() default: } - s.outMu.Lock() - defer s.outMu.Unlock() - n, err := fmt.Fprintf(s.out, "Content-Length: %v\r\n\r\n", len(data)) + data, err := json.Marshal(msg) + if err != nil { + return 0, fmt.Errorf("marshaling message: %v", err) + } + n, err := fmt.Fprintf(s.conn, "Content-Length: %v\r\n\r\n", len(data)) total := int64(n) if err == nil { - n, err = s.out.Write(data) + n, err = s.conn.Write(data) total += int64(n) } return total, err } + +func (s *headerStream) Close() error { + return s.conn.Close() +} diff --git a/internal/vendored/go-tools/jsonrpc2/wire.go b/internal/vendored/go-tools/jsonrpc2/wire.go index 127feb17..cfef6c28 100644 --- a/internal/vendored/go-tools/jsonrpc2/wire.go +++ b/internal/vendored/go-tools/jsonrpc2/wire.go @@ -7,37 +7,37 @@ package jsonrpc2 import ( "encoding/json" "fmt" - "strconv" + "math" ) // this file contains the go forms of the wire specification // see http://www.jsonrpc.org/specification for details -const ( - // CodeUnknownError should be used for all non coded errors. - CodeUnknownError = -32001 - // CodeParseError is used when invalid JSON was received by the server. - CodeParseError = -32700 - //CodeInvalidRequest is used when the JSON sent is not a valid Request object. - CodeInvalidRequest = -32600 - // CodeMethodNotFound should be returned by the handler when the method does +var ( + // ErrUnknown should be used for all non coded errors. + ErrUnknown = NewError(-32001, "JSON RPC unknown error") + // ErrParse is used when invalid JSON was received by the server. + ErrParse = NewError(-32700, "JSON RPC parse error") + //ErrInvalidRequest is used when the JSON sent is not a valid Request object. + ErrInvalidRequest = NewError(-32600, "JSON RPC invalid request") + // ErrMethodNotFound should be returned by the handler when the method does // not exist / is not available. - CodeMethodNotFound = -32601 - // CodeInvalidParams should be returned by the handler when method + ErrMethodNotFound = NewError(-32601, "JSON RPC method not found") + // ErrInvalidParams should be returned by the handler when method // parameter(s) were invalid. - CodeInvalidParams = -32602 - // CodeInternalError is not currently returned but defined for completeness. - CodeInternalError = -32603 + ErrInvalidParams = NewError(-32602, "JSON RPC invalid params") + // ErrInternal is not currently returned but defined for completeness. + ErrInternal = NewError(-32603, "JSON RPC internal error") - //CodeServerOverloaded is returned when a message was refused due to a + //ErrServerOverloaded is returned when a message was refused due to a //server being temporarily unable to accept any new messages. - CodeServerOverloaded = -32000 + ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded") ) -// WireRequest is sent to a server to represent a Call or Notify operaton. -type WireRequest struct { +// wireRequest is sent to a server to represent a Call or Notify operaton. +type wireRequest struct { // VersionTag is always encoded as the string "2.0" - VersionTag VersionTag `json:"jsonrpc"` + VersionTag wireVersionTag `json:"jsonrpc"` // Method is a string containing the method name to invoke. Method string `json:"method"` // Params is either a struct or an array with the parameters of the method. @@ -52,19 +52,30 @@ type WireRequest struct { // It will always have the ID field set to tie it back to a request, and will // have either the Result or Error fields set depending on whether it is a // success or failure response. -type WireResponse struct { +type wireResponse struct { // VersionTag is always encoded as the string "2.0" - VersionTag VersionTag `json:"jsonrpc"` + VersionTag wireVersionTag `json:"jsonrpc"` // Result is the response value, and is required on success. Result *json.RawMessage `json:"result,omitempty"` // Error is a structured error response if the call fails. - Error *Error `json:"error,omitempty"` + Error *wireError `json:"error,omitempty"` // ID must be set and is the identifier of the Request this is a response to. ID *ID `json:"id,omitempty"` } -// Error represents a structured error in a Response. -type Error struct { +// wireCombined has all the fields of both Request and Response. +// We can decode this and then work out which it is. +type wireCombined struct { + VersionTag wireVersionTag `json:"jsonrpc"` + ID *ID `json:"id,omitempty"` + Method string `json:"method"` + Params *json.RawMessage `json:"params,omitempty"` + Result *json.RawMessage `json:"result,omitempty"` + Error *wireError `json:"error,omitempty"` +} + +// wireError represents a structured error in a Response. +type wireError struct { // Code is an error code indicating the type of failure. Code int64 `json:"code"` // Message is a short description of the error. @@ -73,32 +84,34 @@ type Error struct { Data *json.RawMessage `json:"data"` } -// VersionTag is a special 0 sized struct that encodes as the jsonrpc version +// wireVersionTag is a special 0 sized struct that encodes as the jsonrpc version // tag. // It will fail during decode if it is not the correct version tag in the // stream. -type VersionTag struct{} +type wireVersionTag struct{} // ID is a Request identifier. -// Only one of either the Name or Number members will be set, using the -// number form if the Name is the empty string. type ID struct { - Name string - Number int64 + name string + number int64 } -func (err *Error) Error() string { - if err == nil { - return "" +func NewError(code int64, message string) error { + return &wireError{ + Code: code, + Message: message, } +} + +func (err *wireError) Error() string { return err.Message } -func (VersionTag) MarshalJSON() ([]byte, error) { +func (wireVersionTag) MarshalJSON() ([]byte, error) { return json.Marshal("2.0") } -func (VersionTag) UnmarshalJSON(data []byte) error { +func (wireVersionTag) UnmarshalJSON(data []byte) error { version := "" if err := json.Unmarshal(data, &version); err != nil { return err @@ -109,30 +122,41 @@ func (VersionTag) UnmarshalJSON(data []byte) error { return nil } -// String returns a string representation of the ID. -// The representation is non ambiguous, string forms are quoted, number forms -// are preceded by a # -func (id *ID) String() string { - if id == nil { - return "" +const invalidID int64 = math.MaxInt64 + +// NewIntID returns a new numerical request ID. +func NewIntID(v int64) ID { return ID{number: v} } + +// NewStringID returns a new string request ID. +func NewStringID(v string) ID { return ID{name: v} } + +// Format writes the ID to the formatter. +// If the rune is q the representation is non ambiguous, +// string forms are quoted, number forms are preceded by a # +func (id ID) Format(f fmt.State, r rune) { + numF, strF := `%d`, `%s` + if r == 'q' { + numF, strF = `#%d`, `%q` } - if id.Name != "" { - return strconv.Quote(id.Name) + switch { + case id.name != "": + fmt.Fprintf(f, strF, id.name) + default: + fmt.Fprintf(f, numF, id.number) } - return "#" + strconv.FormatInt(id.Number, 10) } func (id *ID) MarshalJSON() ([]byte, error) { - if id.Name != "" { - return json.Marshal(id.Name) + if id.name != "" { + return json.Marshal(id.name) } - return json.Marshal(id.Number) + return json.Marshal(id.number) } func (id *ID) UnmarshalJSON(data []byte) error { *id = ID{} - if err := json.Unmarshal(data, &id.Number); err == nil { + if err := json.Unmarshal(data, &id.number); err == nil { return nil } - return json.Unmarshal(data, &id.Name) + return json.Unmarshal(data, &id.name) } diff --git a/internal/vendored/go-tools/jsonrpc2/wire_test.go b/internal/vendored/go-tools/jsonrpc2/wire_test.go new file mode 100644 index 00000000..d6b96339 --- /dev/null +++ b/internal/vendored/go-tools/jsonrpc2/wire_test.go @@ -0,0 +1,116 @@ +// 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 jsonrpc2_test + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" +) + +var wireIDTestData = []struct { + name string + id jsonrpc2.ID + encoded []byte + plain string + quoted string +}{ + { + name: `empty`, + encoded: []byte(`0`), + plain: `0`, + quoted: `#0`, + }, { + name: `number`, + id: jsonrpc2.NewIntID(43), + encoded: []byte(`43`), + plain: `43`, + quoted: `#43`, + }, { + name: `string`, + id: jsonrpc2.NewStringID("life"), + encoded: []byte(`"life"`), + plain: `life`, + quoted: `"life"`, + }, +} + +func TestIDFormat(t *testing.T) { + for _, test := range wireIDTestData { + t.Run(test.name, func(t *testing.T) { + if got := fmt.Sprint(test.id); got != test.plain { + t.Errorf("got %s expected %s", got, test.plain) + } + if got := fmt.Sprintf("%q", test.id); got != test.quoted { + t.Errorf("got %s want %s", got, test.quoted) + } + }) + } +} + +func TestIDEncode(t *testing.T) { + for _, test := range wireIDTestData { + t.Run(test.name, func(t *testing.T) { + data, err := json.Marshal(&test.id) + if err != nil { + t.Fatal(err) + } + checkJSON(t, data, test.encoded) + }) + } +} + +func TestIDDecode(t *testing.T) { + for _, test := range wireIDTestData { + t.Run(test.name, func(t *testing.T) { + var got *jsonrpc2.ID + if err := json.Unmarshal(test.encoded, &got); err != nil { + t.Fatal(err) + } + if got == nil { + t.Errorf("got nil want %s", test.id) + } else if *got != test.id { + t.Errorf("got %s want %s", got, test.id) + } + }) + } +} + +func TestErrorResponse(t *testing.T) { + // originally reported in #39719, this checks that result is not present if + // it is an error response + r, _ := jsonrpc2.NewResponse(jsonrpc2.NewIntID(3), nil, fmt.Errorf("computing fix edits")) + data, err := json.Marshal(r) + if err != nil { + t.Fatal(err) + } + checkJSON(t, data, []byte(`{ + "jsonrpc":"2.0", + "error":{ + "code":0, + "message":"computing fix edits", + "data":null + }, + "id":3 + }`)) +} + +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, []byte(got)); err != nil { + t.Fatal(err) + } + w := &bytes.Buffer{} + if err := json.Compact(w, []byte(want)); err != nil { + t.Fatal(err) + } + if g.String() != w.String() { + t.Fatalf("Got:\n%s\nWant:\n%s", g, w) + } +} diff --git a/internal/vendored/go-tools/lsp/debug/tag/tag.go b/internal/vendored/go-tools/lsp/debug/tag/tag.go index 9997a1dc..515e6990 100644 --- a/internal/vendored/go-tools/lsp/debug/tag/tag.go +++ b/internal/vendored/go-tools/lsp/debug/tag/tag.go @@ -6,40 +6,49 @@ package tag import ( - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/keys" ) var ( - // create the tag keys we use - Method = event.NewStringKey("method", "") - StatusCode = event.NewStringKey("status.code", "") - StatusMessage = event.NewStringKey("status.message", "") - RPCID = event.NewStringKey("id", "") - RPCDirection = event.NewStringKey("direction", "") - File = event.NewStringKey("file", "") - Directory = event.NewKey("directory", "") - URI = event.NewKey("URI", "") - Package = event.NewStringKey("package", "") - PackagePath = event.NewStringKey("package_path", "") - Query = event.NewKey("query", "") - Snapshot = event.NewUInt64Key("snapshot", "") - Operation = event.NewStringKey("operation", "") - - Position = event.NewKey("position", "") - Category = event.NewStringKey("category", "") - PackageCount = event.NewIntKey("packages", "") - Files = event.NewKey("files", "") - Port = event.NewIntKey("port", "") - Type = event.NewKey("type", "") - HoverKind = event.NewStringKey("hoverkind", "") + // create the label keys we use + Method = keys.NewString("method", "") + StatusCode = keys.NewString("status.code", "") + StatusMessage = keys.NewString("status.message", "") + RPCID = keys.NewString("id", "") + RPCDirection = keys.NewString("direction", "") + File = keys.NewString("file", "") + Directory = keys.New("directory", "") + URI = keys.New("URI", "") + Package = keys.NewString("package", "") + PackagePath = keys.NewString("package_path", "") + Query = keys.New("query", "") + Snapshot = keys.NewUInt64("snapshot", "") + Operation = keys.NewString("operation", "") + + Position = keys.New("position", "") + Category = keys.NewString("category", "") + PackageCount = keys.NewInt("packages", "") + Files = keys.New("files", "") + Port = keys.NewInt("port", "") + Type = keys.New("type", "") + HoverKind = keys.NewString("hoverkind", "") + + NewServer = keys.NewString("new_server", "A new server was added") + EndServer = keys.NewString("end_server", "A server was shut down") + + ServerID = keys.NewString("server", "The server ID an event is related to") + Logfile = keys.NewString("logfile", "") + DebugAddress = keys.NewString("debug_address", "") + GoplsPath = keys.NewString("gopls_path", "") + ClientID = keys.NewString("client_id", "") ) var ( // create the stats we measure - Started = event.NewInt64Key("started", "Count of started RPCs.") - ReceivedBytes = event.NewInt64Key("received_bytes", "Bytes received.") //, unit.Bytes) - SentBytes = event.NewInt64Key("sent_bytes", "Bytes sent.") //, unit.Bytes) - Latency = event.NewFloat64Key("latency_ms", "Elapsed time in milliseconds") //, unit.Milliseconds) + Started = keys.NewInt64("started", "Count of started RPCs.") + ReceivedBytes = keys.NewInt64("received_bytes", "Bytes received.") //, unit.Bytes) + SentBytes = keys.NewInt64("sent_bytes", "Bytes sent.") //, unit.Bytes) + Latency = keys.NewFloat64("latency_ms", "Elapsed time in milliseconds") //, unit.Milliseconds) ) const ( diff --git a/internal/vendored/go-tools/lsp/protocol/context.go b/internal/vendored/go-tools/lsp/protocol/context.go index ca831817..1157cc54 100644 --- a/internal/vendored/go-tools/lsp/protocol/context.go +++ b/internal/vendored/go-tools/lsp/protocol/context.go @@ -1,10 +1,13 @@ package protocol import ( + "bytes" "context" - "fmt" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/core" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/export" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event/label" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/xcontext" ) @@ -18,16 +21,19 @@ func WithClient(ctx context.Context, client Client) context.Context { return context.WithValue(ctx, clientKey, client) } -func LogEvent(ctx context.Context, ev event.Event, tags event.TagMap) context.Context { - if !ev.IsLog() { +func LogEvent(ctx context.Context, ev core.Event, tags label.Map) context.Context { + if !event.IsLog(ev) { return ctx } client, ok := ctx.Value(clientKey).(Client) if !ok { return ctx } - msg := &LogMessageParams{Type: Info, Message: fmt.Sprint(ev)} - if event.Err.Get(tags) != nil { + buf := &bytes.Buffer{} + p := export.Printer{} + p.WriteEvent(buf, ev, tags) + msg := &LogMessageParams{Type: Info, Message: buf.String()} + if event.IsError(ev) { msg.Type = Error } go client.LogMessage(xcontext.Detach(ctx), msg) diff --git a/internal/vendored/go-tools/lsp/protocol/log.go b/internal/vendored/go-tools/lsp/protocol/log.go index 247542d1..a9bcf2d7 100644 --- a/internal/vendored/go-tools/lsp/protocol/log.go +++ b/internal/vendored/go-tools/lsp/protocol/log.go @@ -2,7 +2,6 @@ package protocol import ( "context" - "encoding/json" "fmt" "io" "strings" @@ -14,37 +13,31 @@ import ( type loggingStream struct { stream jsonrpc2.Stream + logMu sync.Mutex log io.Writer } // LoggingStream returns a stream that does LSP protocol logging too func LoggingStream(str jsonrpc2.Stream, w io.Writer) jsonrpc2.Stream { - return &loggingStream{str, w} + return &loggingStream{stream: str, log: w} } -func (s *loggingStream) Read(ctx context.Context) ([]byte, int64, error) { - data, count, err := s.stream.Read(ctx) +func (s *loggingStream) Read(ctx context.Context) (jsonrpc2.Message, int64, error) { + msg, count, err := s.stream.Read(ctx) if err == nil { - logIn(s.log, data) + s.logCommon(msg, true) } - return data, count, err + return msg, count, err } -func (s *loggingStream) Write(ctx context.Context, data []byte) (int64, error) { - logOut(s.log, data) - count, err := s.stream.Write(ctx, data) +func (s *loggingStream) Write(ctx context.Context, msg jsonrpc2.Message) (int64, error) { + s.logCommon(msg, false) + count, err := s.stream.Write(ctx, msg) return count, err } -// Combined has all the fields of both Request and Response. -// We can decode this and then work out which it is. -type Combined struct { - VersionTag jsonrpc2.VersionTag `json:"jsonrpc"` - ID *jsonrpc2.ID `json:"id,omitempty"` - Method string `json:"method"` - Params *json.RawMessage `json:"params,omitempty"` - Result *json.RawMessage `json:"result,omitempty"` - Error *jsonrpc2.Error `json:"error,omitempty"` +func (s *loggingStream) Close() error { + return s.stream.Close() } type req struct { @@ -67,23 +60,19 @@ var maps = &mapped{ // these 4 methods are each used exactly once, but it seemed // better to have the encapsulation rather than ad hoc mutex // code in 4 places -func (m *mapped) client(id string, del bool) req { +func (m *mapped) client(id string) req { m.mu.Lock() defer m.mu.Unlock() v := m.clientCalls[id] - if del { - delete(m.clientCalls, id) - } + delete(m.clientCalls, id) return v } -func (m *mapped) server(id string, del bool) req { +func (m *mapped) server(id string) req { m.mu.Lock() defer m.mu.Unlock() v := m.serverCalls[id] - if del { - delete(m.serverCalls, id) - } + delete(m.serverCalls, id) return v } @@ -101,144 +90,43 @@ func (m *mapped) setServer(id string, r req) { const eor = "\r\n\r\n\r\n" -func strID(x *jsonrpc2.ID) string { - if x == nil { - // should never happen, but we need a number - return "999999999" - } - if x.Name != "" { - return x.Name - } - return fmt.Sprintf("%d", x.Number) -} - -func logCommon(outfd io.Writer, data []byte) (*Combined, time.Time, string) { - if outfd == nil { - return nil, time.Time{}, "" +func (s *loggingStream) logCommon(msg jsonrpc2.Message, isRead bool) { + s.logMu.Lock() + defer s.logMu.Unlock() + direction, pastTense := "Received", "Received" + get, set := maps.client, maps.setServer + if isRead { + direction, pastTense = "Sending", "Sent" + get, set = maps.server, maps.setClient } - var v Combined - err := json.Unmarshal(data, &v) - if err != nil { - fmt.Fprintf(outfd, "Unmarshal %v\n", err) - panic(err) // do better + if msg == nil || s.log == nil { + return } tm := time.Now() tmfmt := tm.Format("15:04:05.000 PM") - return &v, tm, tmfmt -} - -// logOut and logIn could be combined. "received"<->"Sending", serverCalls<->clientCalls -// but it wouldn't be a lot shorter or clearer and "shutdown" is a special case - -// Writing a message to the client, log it -func logOut(outfd io.Writer, data []byte) { - v, tm, tmfmt := logCommon(outfd, data) - if v == nil { - return - } - if v.Error != nil { - id := strID(v.ID) - fmt.Fprintf(outfd, "[Error - %s] Received #%s %s%s", tmfmt, id, v.Error, eor) - return - } - buf := strings.Builder{} - id := strID(v.ID) - fmt.Fprintf(&buf, "[Trace - %s] ", tmfmt) // common beginning - if v.ID != nil && v.Method != "" && v.Params != nil { - fmt.Fprintf(&buf, "Received request '%s - (%s)'.\n", v.Method, id) - fmt.Fprintf(&buf, "Params: %s%s", *v.Params, eor) - maps.setServer(id, req{method: v.Method, start: tm}) - } else if v.ID != nil && v.Method == "" && v.Params == nil { - cc := maps.client(id, true) - elapsed := tm.Sub(cc.start) - fmt.Fprintf(&buf, "Received response '%s - (%s)' in %dms.\n", - cc.method, id, elapsed/time.Millisecond) - if v.Result == nil { - fmt.Fprintf(&buf, "Result: {}%s", eor) - } else { - fmt.Fprintf(&buf, "Result: %s%s", string(*v.Result), eor) - } - } else if v.ID == nil && v.Method != "" && v.Params != nil { - p := "null" - if v.Params != nil { - p = string(*v.Params) - } - fmt.Fprintf(&buf, "Received notification '%s'.\n", v.Method) - fmt.Fprintf(&buf, "Params: %s%s", p, eor) - } else { // for completeness, as it should never happen - buf = strings.Builder{} // undo common Trace - fmt.Fprintf(&buf, "[Error - %s] on write ID?%v method:%q Params:%v Result:%v Error:%v%s", - tmfmt, v.ID != nil, v.Method, v.Params != nil, - v.Result != nil, v.Error != nil, eor) - p := "null" - if v.Params != nil { - p = string(*v.Params) - } - r := "null" - if v.Result != nil { - r = string(*v.Result) - } - fmt.Fprintf(&buf, "%s\n%s\n%s%s", p, r, v.Error, eor) - } - outfd.Write([]byte(buf.String())) -} -// Got a message from the client, log it -func logIn(outfd io.Writer, data []byte) { - v, tm, tmfmt := logCommon(outfd, data) - if v == nil { - return - } - // ID Method Params => Sending request - // ID !Method Result(might be null, but !Params) => Sending response (could we get an Error?) - // !ID Method Params => Sending notification - if v.Error != nil { // does this ever happen? - id := strID(v.ID) - fmt.Fprintf(outfd, "[Error - %s] Sent #%s %s%s", tmfmt, id, v.Error, eor) - return - } buf := strings.Builder{} - id := strID(v.ID) fmt.Fprintf(&buf, "[Trace - %s] ", tmfmt) // common beginning - if v.ID != nil && v.Method != "" && (v.Params != nil || v.Method == "shutdown") { - fmt.Fprintf(&buf, "Sending request '%s - (%s)'.\n", v.Method, id) - x := "{}" - if v.Params != nil { - x = string(*v.Params) - } - fmt.Fprintf(&buf, "Params: %s%s", x, eor) - maps.setClient(id, req{method: v.Method, start: tm}) - } else if v.ID != nil && v.Method == "" && v.Params == nil { - sc := maps.server(id, true) - elapsed := tm.Sub(sc.start) - fmt.Fprintf(&buf, "Sending response '%s - (%s)' took %dms.\n", - sc.method, id, elapsed/time.Millisecond) - if v.Result == nil { - fmt.Fprintf(&buf, "Result: {}%s", eor) - } else { - fmt.Fprintf(&buf, "Result: %s%s", string(*v.Result), eor) + switch msg := msg.(type) { + case *jsonrpc2.Call: + id := fmt.Sprint(msg.ID()) + fmt.Fprintf(&buf, "%s request '%s - (%s)'.\n", direction, msg.Method(), id) + fmt.Fprintf(&buf, "Params: %s%s", msg.Params(), eor) + set(id, req{method: msg.Method(), start: tm}) + case *jsonrpc2.Notification: + fmt.Fprintf(&buf, "%s notification '%s'.\n", direction, msg.Method()) + fmt.Fprintf(&buf, "Params: %s%s", msg.Params(), eor) + case *jsonrpc2.Response: + id := fmt.Sprint(msg.ID()) + if err := msg.Err(); err != nil { + fmt.Fprintf(s.log, "[Error - %s] %s #%s %s%s", pastTense, tmfmt, id, err, eor) + return } - } else if v.ID == nil && v.Method != "" { - p := "null" - if v.Params != nil { - p = string(*v.Params) - } - fmt.Fprintf(&buf, "Sending notification '%s'.\n", v.Method) - fmt.Fprintf(&buf, "Params: %s%s", p, eor) - } else { // for completeness, as it should never happen - buf = strings.Builder{} // undo common Trace - fmt.Fprintf(&buf, "[Error - %s] on read ID?%v method:%q Params:%v Result:%v Error:%v%s", - tmfmt, v.ID != nil, v.Method, v.Params != nil, - v.Result != nil, v.Error != nil, eor) - p := "null" - if v.Params != nil { - p = string(*v.Params) - } - r := "null" - if v.Result != nil { - r = string(*v.Result) - } - fmt.Fprintf(&buf, "%s\n%s\n%s%s", p, r, v.Error, eor) + cc := get(id) + elapsed := tm.Sub(cc.start) + fmt.Fprintf(&buf, "%s response '%s - (%s)' in %dms.\n", + direction, cc.method, id, elapsed/time.Millisecond) + fmt.Fprintf(&buf, "Result: %s%s", msg.Result(), eor) } - outfd.Write([]byte(buf.String())) + s.log.Write([]byte(buf.String())) } diff --git a/internal/vendored/go-tools/lsp/protocol/protocol.go b/internal/vendored/go-tools/lsp/protocol/protocol.go index a3032831..9e905b02 100644 --- a/internal/vendored/go-tools/lsp/protocol/protocol.go +++ b/internal/vendored/go-tools/lsp/protocol/protocol.go @@ -9,96 +9,129 @@ import ( "encoding/json" "fmt" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/event" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/xcontext" ) -const ( +var ( // RequestCancelledError should be used when a request is cancelled early. - RequestCancelledError = -32800 + RequestCancelledError = jsonrpc2.NewError(-32800, "JSON RPC cancelled") ) -type clientHandler struct { - jsonrpc2.EmptyHandler - client Client +// ClientDispatcher returns a Client that dispatches LSP requests across the +// given jsonrpc2 connection. +func ClientDispatcher(conn jsonrpc2.Conn) Client { + return &clientDispatcher{Conn: conn} } -// ClientHandler returns a jsonrpc2.Handler that handles the LSP client -// protocol. -func ClientHandler(client Client) jsonrpc2.Handler { - return &clientHandler{client: client} +type clientDispatcher struct { + jsonrpc2.Conn } -type serverHandler struct { - jsonrpc2.EmptyHandler - server Server +// ServerDispatcher returns a Server that dispatches LSP requests across the +// given jsonrpc2 connection. +func ServerDispatcher(conn jsonrpc2.Conn) Server { + return &serverDispatcher{Conn: conn} } -// ServerHandler returns a jsonrpc2.Handler that handles the LSP server -// protocol. -func ServerHandler(server Server) jsonrpc2.Handler { - return &serverHandler{server: server} +type serverDispatcher struct { + jsonrpc2.Conn } -// ClientDispatcher returns a Client that dispatches LSP requests across the -// given jsonrpc2 connection. -func ClientDispatcher(conn *jsonrpc2.Conn) Client { - return &clientDispatcher{Conn: conn} +func ClientHandler(client Client, handler jsonrpc2.Handler) jsonrpc2.Handler { + return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + if ctx.Err() != nil { + ctx := xcontext.Detach(ctx) + return reply(ctx, nil, RequestCancelledError) + } + handled, err := clientDispatch(ctx, client, reply, req) + if handled || err != nil { + return err + } + return handler(ctx, reply, req) + } } -// ServerDispatcher returns a Server that dispatches LSP requests across the -// given jsonrpc2 connection. -func ServerDispatcher(conn *jsonrpc2.Conn) Server { - return &serverDispatcher{Conn: conn} -} +func ServerHandler(server Server, handler jsonrpc2.Handler) jsonrpc2.Handler { + return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + if ctx.Err() != nil { + ctx := xcontext.Detach(ctx) + return reply(ctx, nil, RequestCancelledError) + } + handled, err := serverDispatch(ctx, server, reply, req) + if handled || err != nil { + return err + } + //TODO: This code is wrong, it ignores handler and assumes non standard + // request handles everything + // non standard request should just be a layered handler. + var params interface{} + if err := json.Unmarshal(req.Params(), ¶ms); err != nil { + return sendParseError(ctx, reply, err) + } + resp, err := server.NonstandardRequest(ctx, req.Method(), params) + return reply(ctx, resp, err) -// Canceller is a jsonrpc2.Handler that handles LSP request cancellation. -type Canceller struct{ jsonrpc2.EmptyHandler } + } +} +func Handlers(handler jsonrpc2.Handler) jsonrpc2.Handler { + return CancelHandler( + jsonrpc2.AsyncHandler( + jsonrpc2.MustReplyHandler(handler))) +} -func (Canceller) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context { - if direction == jsonrpc2.Receive && r.Method == "$/cancelRequest" { +func CancelHandler(handler jsonrpc2.Handler) jsonrpc2.Handler { + handler, canceller := jsonrpc2.CancelHandler(handler) + return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + if req.Method() != "$/cancelRequest" { + // TODO(iancottrell): See if we can generate a reply for the request to be cancelled + // at the point of cancellation rather than waiting for gopls to naturally reply. + // To do that, we need to keep track of whether a reply has been sent already and + // be careful about racing between the two paths. + // TODO(iancottrell): Add a test that watches the stream and verifies the response + // for the cancelled request flows. + replyWithDetachedContext := func(ctx context.Context, resp interface{}, err error) error { + // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest + if ctx.Err() != nil && err == nil { + err = RequestCancelledError + } + ctx = xcontext.Detach(ctx) + return reply(ctx, resp, err) + } + return handler(ctx, replyWithDetachedContext, req) + } var params CancelParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(req.Params(), ¶ms); err != nil { + return sendParseError(ctx, reply, err) + } + if n, ok := params.ID.(float64); ok { + canceller(jsonrpc2.NewIntID(int64(n))) + } else if s, ok := params.ID.(string); ok { + canceller(jsonrpc2.NewStringID(s)) } else { - v := jsonrpc2.ID{} - if n, ok := params.ID.(float64); ok { - v.Number = int64(n) - } else if s, ok := params.ID.(string); ok { - v.Name = s - } else { - event.Error(ctx, fmt.Sprintf("Request ID %v malformed", params.ID), nil) - return ctx - } - conn.Cancel(v) + return sendParseError(ctx, reply, fmt.Errorf("request ID %v malformed", params.ID)) } + return reply(ctx, nil, nil) } - return ctx } -func (Canceller) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool { - if cancelled { - return false +func Call(ctx context.Context, conn jsonrpc2.Conn, method string, params interface{}, result interface{}) error { + id, err := conn.Call(ctx, method, params, result) + if ctx.Err() != nil { + cancelCall(ctx, conn, id) } + return err +} + +func cancelCall(ctx context.Context, conn jsonrpc2.Conn, id jsonrpc2.ID) { ctx = xcontext.Detach(ctx) - ctx, done := event.StartSpan(ctx, "protocol.canceller") + ctx, done := event.Start(ctx, "protocol.canceller") defer done() // Note that only *jsonrpc2.ID implements json.Marshaler. conn.Notify(ctx, "$/cancelRequest", &CancelParams{ID: &id}) - return true } -func (Canceller) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool { - // Hide cancellations from downstream handlers. - return r.Method == "$/cancelRequest" -} - -func sendParseError(ctx context.Context, req *jsonrpc2.Request, err error) { - if _, ok := err.(*jsonrpc2.Error); !ok { - err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) - } - if err := req.Reply(ctx, nil, err); err != nil { - event.Error(ctx, "", err) - } +func sendParseError(ctx context.Context, reply jsonrpc2.Replier, err error) error { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) } diff --git a/internal/vendored/go-tools/lsp/protocol/tsclient.go b/internal/vendored/go-tools/lsp/protocol/tsclient.go index 71fe8a02..a3bcb618 100644 --- a/internal/vendored/go-tools/lsp/protocol/tsclient.go +++ b/internal/vendored/go-tools/lsp/protocol/tsclient.go @@ -2,18 +2,17 @@ package protocol // Package protocol contains data types and code for LSP jsonrpcs // generated automatically from vscode-languageserver-node -// commit: 151b520c995ee3d76729b5c46258ab273d989726 -// last fetched Fri Mar 13 2020 17:02:20 GMT-0400 (Eastern Daylight Time) +// commit: 1f688e2f65f3a6fc9ba395380cd7b059667a9ecf +// last fetched Tue Jun 09 2020 11:22:02 GMT-0400 (Eastern Daylight Time) // Code generated (see typescript/README.md) DO NOT EDIT. import ( "context" "encoding/json" + "fmt" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/xcontext" ) type Client interface { @@ -31,152 +30,97 @@ type Client interface { ApplyEdit(context.Context, *ApplyWorkspaceEditParams) (*ApplyWorkspaceEditResponse, error) } -func (h clientHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool { - if delivered { - return false - } - if ctx.Err() != nil { - ctx := xcontext.Detach(ctx) - r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, "")) - return true - } - switch r.Method { +func clientDispatch(ctx context.Context, client Client, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { + switch r.Method() { case "window/showMessage": // notif var params ShowMessageParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.client.ShowMessage(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := client.ShowMessage(ctx, ¶ms) + return true, reply(ctx, nil, err) case "window/logMessage": // notif var params LogMessageParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.client.LogMessage(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := client.LogMessage(ctx, ¶ms) + return true, reply(ctx, nil, err) case "telemetry/event": // notif var params interface{} - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.client.Event(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := client.Event(ctx, ¶ms) + return true, reply(ctx, nil, err) case "textDocument/publishDiagnostics": // notif var params PublishDiagnosticsParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.client.PublishDiagnostics(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := client.PublishDiagnostics(ctx, ¶ms) + return true, reply(ctx, nil, err) case "$/progress": // notif var params ProgressParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.client.Progress(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := client.Progress(ctx, ¶ms) + return true, reply(ctx, nil, err) case "workspace/workspaceFolders": // req - if r.Params != nil { - r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) - return true + if len(r.Params()) > 0 { + return true, reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)) } - resp, err := h.client.WorkspaceFolders(ctx) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := client.WorkspaceFolders(ctx) + return true, reply(ctx, resp, err) case "workspace/configuration": // req var params ParamConfiguration - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.client.Configuration(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := client.Configuration(ctx, ¶ms) + return true, reply(ctx, resp, err) case "window/workDoneProgress/create": // req var params WorkDoneProgressCreateParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - err := h.client.WorkDoneProgressCreate(ctx, ¶ms) - if err := r.Reply(ctx, nil, err); err != nil { - event.Error(ctx, "", err) - } - return true + err := client.WorkDoneProgressCreate(ctx, ¶ms) + return true, reply(ctx, nil, err) case "client/registerCapability": // req var params RegistrationParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - err := h.client.RegisterCapability(ctx, ¶ms) - if err := r.Reply(ctx, nil, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := client.RegisterCapability(ctx, ¶ms) + return true, reply(ctx, nil, err) case "client/unregisterCapability": // req var params UnregistrationParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - err := h.client.UnregisterCapability(ctx, ¶ms) - if err := r.Reply(ctx, nil, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := client.UnregisterCapability(ctx, ¶ms) + return true, reply(ctx, nil, err) case "window/showMessageRequest": // req var params ShowMessageRequestParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.client.ShowMessageRequest(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := client.ShowMessageRequest(ctx, ¶ms) + return true, reply(ctx, resp, err) case "workspace/applyEdit": // req var params ApplyWorkspaceEditParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.client.ApplyEdit(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true - default: - return false + resp, err := client.ApplyEdit(ctx, ¶ms) + return true, reply(ctx, resp, err) + default: + return false, nil } } -type clientDispatcher struct { - *jsonrpc2.Conn -} - func (s *clientDispatcher) ShowMessage(ctx context.Context, params *ShowMessageParams) error { return s.Conn.Notify(ctx, "window/showMessage", params) } @@ -198,7 +142,7 @@ func (s *clientDispatcher) Progress(ctx context.Context, params *ProgressParams) } func (s *clientDispatcher) WorkspaceFolders(ctx context.Context) ([]WorkspaceFolder /*WorkspaceFolder[] | null*/, error) { var result []WorkspaceFolder /*WorkspaceFolder[] | null*/ - if err := s.Conn.Call(ctx, "workspace/workspaceFolders", nil, &result); err != nil { + if err := Call(ctx, s.Conn, "workspace/workspaceFolders", nil, &result); err != nil { return nil, err } return result, nil @@ -206,27 +150,27 @@ func (s *clientDispatcher) WorkspaceFolders(ctx context.Context) ([]WorkspaceFol func (s *clientDispatcher) Configuration(ctx context.Context, params *ParamConfiguration) ([]interface{}, error) { var result []interface{} - if err := s.Conn.Call(ctx, "workspace/configuration", params, &result); err != nil { + if err := Call(ctx, s.Conn, "workspace/configuration", params, &result); err != nil { return nil, err } return result, nil } func (s *clientDispatcher) WorkDoneProgressCreate(ctx context.Context, params *WorkDoneProgressCreateParams) error { - return s.Conn.Call(ctx, "window/workDoneProgress/create", params, nil) // Call, not Notify + return Call(ctx, s.Conn, "window/workDoneProgress/create", params, nil) // Call, not Notify } func (s *clientDispatcher) RegisterCapability(ctx context.Context, params *RegistrationParams) error { - return s.Conn.Call(ctx, "client/registerCapability", params, nil) // Call, not Notify + return Call(ctx, s.Conn, "client/registerCapability", params, nil) // Call, not Notify } func (s *clientDispatcher) UnregisterCapability(ctx context.Context, params *UnregistrationParams) error { - return s.Conn.Call(ctx, "client/unregisterCapability", params, nil) // Call, not Notify + return Call(ctx, s.Conn, "client/unregisterCapability", params, nil) // Call, not Notify } func (s *clientDispatcher) ShowMessageRequest(ctx context.Context, params *ShowMessageRequestParams) (*MessageActionItem /*MessageActionItem | null*/, error) { var result *MessageActionItem /*MessageActionItem | null*/ - if err := s.Conn.Call(ctx, "window/showMessageRequest", params, &result); err != nil { + if err := Call(ctx, s.Conn, "window/showMessageRequest", params, &result); err != nil { return nil, err } return result, nil @@ -234,7 +178,7 @@ func (s *clientDispatcher) ShowMessageRequest(ctx context.Context, params *ShowM func (s *clientDispatcher) ApplyEdit(ctx context.Context, params *ApplyWorkspaceEditParams) (*ApplyWorkspaceEditResponse, error) { var result *ApplyWorkspaceEditResponse - if err := s.Conn.Call(ctx, "workspace/applyEdit", params, &result); err != nil { + if err := Call(ctx, s.Conn, "workspace/applyEdit", params, &result); err != nil { return nil, err } return result, nil diff --git a/internal/vendored/go-tools/lsp/protocol/tsprotocol.go b/internal/vendored/go-tools/lsp/protocol/tsprotocol.go index 221c41a2..63afa034 100644 --- a/internal/vendored/go-tools/lsp/protocol/tsprotocol.go +++ b/internal/vendored/go-tools/lsp/protocol/tsprotocol.go @@ -1,7 +1,7 @@ // Package protocol contains data types and code for LSP jsonrpcs // generated automatically from vscode-languageserver-node -// commit: 151b520c995ee3d76729b5c46258ab273d989726 -// last fetched Fri Mar 13 2020 17:02:20 GMT-0400 (Eastern Daylight Time) +// commit: 1f688e2f65f3a6fc9ba395380cd7b059667a9ecf +// last fetched Tue Jun 09 2020 11:22:02 GMT-0400 (Eastern Daylight Time) package protocol // Code generated (see typescript/README.md) DO NOT EDIT. @@ -44,10 +44,22 @@ type ApplyWorkspaceEditResponse struct { FailedChange float64 `json:"failedChange,omitempty"` } +/** + * @since 3.16.0 + */ +type CallHierarchyClientCapabilities struct { + /** + * Whether implementation supports dynamic registration. If this is set to `true` + * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + /** * Represents an incoming call, e.g. a caller of a method or constructor. * - * @since 3.16.0 - Proposed state + * @since 3.16.0 */ type CallHierarchyIncomingCall struct { /** @@ -55,7 +67,7 @@ type CallHierarchyIncomingCall struct { */ From CallHierarchyItem `json:"from"` /** - * The range at which at which the calls appears. This is relative to the caller + * The ranges at which the calls appear. This is relative to the caller * denoted by [`this.from`](#CallHierarchyIncomingCall.from). */ FromRanges []Range `json:"fromRanges"` @@ -64,7 +76,7 @@ type CallHierarchyIncomingCall struct { /** * The parameter of a `callHierarchy/incomingCalls` request. * - * @since 3.16.0 - Proposed state + * @since 3.16.0 */ type CallHierarchyIncomingCallsParams struct { Item CallHierarchyItem `json:"item"` @@ -76,7 +88,7 @@ type CallHierarchyIncomingCallsParams struct { * Represents programming constructs like functions or constructors in the context * of call hierarchy. * - * @since 3.16.0 - Proposed state + * @since 3.16.0 */ type CallHierarchyItem struct { /** @@ -110,10 +122,19 @@ type CallHierarchyItem struct { SelectionRange Range `json:"selectionRange"` } +/** + * Call hierarchy options used during static registration. + * + * @since 3.16.0 + */ +type CallHierarchyOptions struct { + WorkDoneProgressOptions +} + /** * Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc. * - * @since 3.16.0 - Proposed state + * @since 3.16.0 */ type CallHierarchyOutgoingCall struct { /** @@ -131,7 +152,7 @@ type CallHierarchyOutgoingCall struct { /** * The parameter of a `callHierarchy/outgoingCalls` request. * - * @since 3.16.0 - Proposed state + * @since 3.16.0 */ type CallHierarchyOutgoingCallsParams struct { Item CallHierarchyItem `json:"item"` @@ -142,13 +163,24 @@ type CallHierarchyOutgoingCallsParams struct { /** * The parameter of a `textDocument/prepareCallHierarchy` request. * - * @since 3.16.0 - Proposed state + * @since 3.16.0 */ type CallHierarchyPrepareParams struct { TextDocumentPositionParams WorkDoneProgressParams } +/** + * Call hierarchy options used during static or dynamic registration. + * + * @since 3.16.0 + */ +type CallHierarchyRegistrationOptions struct { + TextDocumentRegistrationOptions + CallHierarchyOptions + StaticRegistrationOptions +} + type CancelParams struct { /** * The request id to cancel. @@ -250,8 +282,9 @@ type CodeActionClientCapabilities struct { */ DynamicRegistration bool `json:"dynamicRegistration,omitempty"` /** - * The client support code action literals as a valid - * response of the `textDocument/codeAction` request. + * The client support code action literals of type `CodeAction` as a valid + * response of the `textDocument/codeAction` request. If the property is not + * set the request can only return `Command` literals. * * @since 3.8.0 */ @@ -669,8 +702,16 @@ type CompletionItem struct { * this completion. When an edit is provided the value of * [insertText](#CompletionItem.insertText) is ignored. * - * *Note:* The text edit's range as well as both ranges from a insert replace edit must be a + * Most editors support two different operation when accepting a completion item. One is to insert a + * completion text and the other is to replace an existing text with a competion text. Since this can + * usually not predetermend by a server it can report both ranges. Clients need to signal support for + * `InsertReplaceEdits` via the `textDocument.completion.insertReplaceSupport` client capability + * property. + * + * *Note 1:* The text edit's range as well as both ranges from a insert replace edit must be a * [single line] and they must contain the position at which completion has been requested. + * *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range must be a prefix of + * the edit's replace range, that means it must be contained and starting at the same position. * * @since 3.16.0 additional type `InsertReplaceEdit` - Proposed state */ @@ -2061,6 +2102,10 @@ type InnerServerCapabilities struct { * The server provides execute command support. */ ExecuteCommandProvider ExecuteCommandOptions `json:"executeCommandProvider,omitempty"` + /** + * The server provides Call Hierarchy support. + */ + CallHierarchyProvider interface{}/* bool | CallHierarchyOptions | CallHierarchyRegistrationOptions*/ `json:"callHierarchyProvider,omitempty"` /** * Experimental server capabilities. */ @@ -2068,7 +2113,7 @@ type InnerServerCapabilities struct { } /** - * A special text edit to provide a insert or a replace operation. + * A special text edit to provide an insert and a replace operation. * * @since 3.16.0 - Proposed state */ @@ -2614,9 +2659,18 @@ type SemanticTokens struct { * @since 3.16.0 - Proposed state */ type SemanticTokensEdit struct { - Start float64 `json:"start"` - DeleteCount float64 `json:"deleteCount"` - Data []float64 `json:"data,omitempty"` + /** + * The start offset of the edit. + */ + Start float64 `json:"start"` + /** + * The count of elements to remove. + */ + DeleteCount float64 `json:"deleteCount"` + /** + * The elements to insert. + */ + Data []float64 `json:"data,omitempty"` } /** @@ -2773,6 +2827,10 @@ type ServerCapabilities = struct { * The server provides execute command support. */ ExecuteCommandProvider ExecuteCommandOptions `json:"executeCommandProvider,omitempty"` + /** + * The server provides Call Hierarchy support. + */ + CallHierarchyProvider interface{}/* bool | CallHierarchyOptions | CallHierarchyRegistrationOptions*/ `json:"callHierarchyProvider,omitempty"` /** * Experimental server capabilities. */ @@ -2868,6 +2926,13 @@ type SignatureHelpClientCapabilities struct { */ LabelOffsetSupport bool `json:"labelOffsetSupport,omitempty"` } `json:"parameterInformation,omitempty"` + /** + * The client support the `activeParameter` property on `SignatureInformation` + * literal. + * + * @since 3.16.0 - proposed state + */ + ActiveParameterSupport bool `json:"activeParameterSupport,omitempty"` } `json:"signatureInformation,omitempty"` /** * The client supports to send additional context information for a @@ -2974,6 +3039,14 @@ type SignatureInformation struct { * The parameters of this signature. */ Parameters []ParameterInformation `json:"parameters,omitempty"` + /** + * The index of the active parameter. + * + * If provided, this is used in place of `SignatureHelp.activeParameter`. + * + * @since 3.16.0 - proposed state + */ + ActiveParameter float64 `json:"activeParameter,omitempty"` } /** @@ -3147,6 +3220,12 @@ type TextDocumentClientCapabilities struct { * Capabilities specific to `textDocument/publishDiagnostics`. */ PublishDiagnostics PublishDiagnosticsClientCapabilities `json:"publishDiagnostics,omitempty"` + /** + * Capabilities specific to the `textDocument/callHierarchy`. + * + * @since 3.16.0 + */ + CallHierarchy CallHierarchyClientCapabilities `json:"callHierarchy,omitempty"` } /** @@ -3304,7 +3383,7 @@ type TextDocumentSyncOptions struct { * If present save notifications are sent to the server. If omitted the notification should not be * sent. */ - Save SaveOptions `json:"save,omitempty"` + Save SaveOptions/*boolean | SaveOptions*/ `json:"save,omitempty"` } /** @@ -3502,11 +3581,10 @@ type WorkDoneProgressParams struct { type WorkDoneProgressReport struct { Kind string `json:"kind"` /** - * Controls enablement state of a cancel button. This property is only valid if a cancel - * button got requested in the `WorkDoneProgressStart` payload. + * Controls enablement state of a cancel button. * - * Clients that don't support cancellation or don't support control the button's - * enablement state are allowed to ignore the setting. + * Clients that don't support cancellation or don't support controlling the button's + * enablement state are allowed to ignore the property. */ Cancellable bool `json:"cancellable,omitempty"` /** @@ -3957,7 +4035,7 @@ const ( * the end of the snippet. Placeholders with equal identifiers are linked, * that is typing in one will update others too. * - * See also: https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/common/snippet.md + * See also: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#snippet_syntax */ SnippetTextFormat InsertTextFormat = 2 diff --git a/internal/vendored/go-tools/lsp/protocol/tsserver.go b/internal/vendored/go-tools/lsp/protocol/tsserver.go index 47779f79..31edf8af 100644 --- a/internal/vendored/go-tools/lsp/protocol/tsserver.go +++ b/internal/vendored/go-tools/lsp/protocol/tsserver.go @@ -2,18 +2,17 @@ package protocol // Package protocol contains data types and code for LSP jsonrpcs // generated automatically from vscode-languageserver-node -// commit: 151b520c995ee3d76729b5c46258ab273d989726 -// last fetched Fri Mar 13 2020 17:02:20 GMT-0400 (Eastern Daylight Time) +// commit: 1f688e2f65f3a6fc9ba395380cd7b059667a9ecf +// last fetched Tue Jun 09 2020 11:22:02 GMT-0400 (Eastern Daylight Time) // Code generated (see typescript/README.md) DO NOT EDIT. import ( "context" "encoding/json" + "fmt" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/xcontext" ) type Server interface { @@ -37,6 +36,9 @@ type Server interface { FoldingRange(context.Context, *FoldingRangeParams) ([]FoldingRange /*FoldingRange[] | null*/, error) Declaration(context.Context, *DeclarationParams) (Declaration /*Declaration | DeclarationLink[] | null*/, error) SelectionRange(context.Context, *SelectionRangeParams) ([]SelectionRange /*SelectionRange[] | null*/, error) + PrepareCallHierarchy(context.Context, *CallHierarchyPrepareParams) ([]CallHierarchyItem /*CallHierarchyItem[] | null*/, error) + IncomingCalls(context.Context, *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/, error) + OutgoingCalls(context.Context, *CallHierarchyOutgoingCallsParams) ([]CallHierarchyOutgoingCall /*CallHierarchyOutgoingCall[] | null*/, error) Initialize(context.Context, *ParamInitialize) (*InitializeResult, error) Shutdown(context.Context) error WillSaveWaitUntil(context.Context, *WillSaveTextDocumentParams) ([]TextEdit /*TextEdit[] | null*/, error) @@ -60,564 +62,358 @@ type Server interface { Rename(context.Context, *RenameParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error) PrepareRename(context.Context, *PrepareRenameParams) (*Range /*Range | { range: Range, placeholder: string } | null*/, error) ExecuteCommand(context.Context, *ExecuteCommandParams) (interface{} /*any | null*/, error) - PrepareCallHierarchy(context.Context, *CallHierarchyPrepareParams) ([]CallHierarchyItem /*CallHierarchyItem[] | null*/, error) - IncomingCalls(context.Context, *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/, error) - OutgoingCalls(context.Context, *CallHierarchyOutgoingCallsParams) ([]CallHierarchyOutgoingCall /*CallHierarchyOutgoingCall[] | null*/, error) SemanticTokens(context.Context, *SemanticTokensParams) (*SemanticTokens /*SemanticTokens | null*/, error) SemanticTokensEdits(context.Context, *SemanticTokensEditsParams) (interface{} /* SemanticTokens | SemanticTokensEdits | nil*/, error) SemanticTokensRange(context.Context, *SemanticTokensRangeParams) (*SemanticTokens /*SemanticTokens | null*/, error) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) } -func (h serverHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool { - if delivered { - return false - } - if ctx.Err() != nil { - ctx := xcontext.Detach(ctx) - r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, "")) - return true - } - switch r.Method { +func serverDispatch(ctx context.Context, server Server, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { + switch r.Method() { case "workspace/didChangeWorkspaceFolders": // notif var params DidChangeWorkspaceFoldersParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.server.DidChangeWorkspaceFolders(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := server.DidChangeWorkspaceFolders(ctx, ¶ms) + return true, reply(ctx, nil, err) case "window/workDoneProgress/cancel": // notif var params WorkDoneProgressCancelParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.server.WorkDoneProgressCancel(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := server.WorkDoneProgressCancel(ctx, ¶ms) + return true, reply(ctx, nil, err) case "initialized": // notif var params InitializedParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.server.Initialized(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := server.Initialized(ctx, ¶ms) + return true, reply(ctx, nil, err) case "exit": // notif - if err := h.server.Exit(ctx); err != nil { - event.Error(ctx, "", err) - } - return true + err := server.Exit(ctx) + return true, reply(ctx, nil, err) case "workspace/didChangeConfiguration": // notif var params DidChangeConfigurationParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.server.DidChangeConfiguration(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := server.DidChangeConfiguration(ctx, ¶ms) + return true, reply(ctx, nil, err) case "textDocument/didOpen": // notif var params DidOpenTextDocumentParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.server.DidOpen(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := server.DidOpen(ctx, ¶ms) + return true, reply(ctx, nil, err) case "textDocument/didChange": // notif var params DidChangeTextDocumentParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.server.DidChange(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := server.DidChange(ctx, ¶ms) + return true, reply(ctx, nil, err) case "textDocument/didClose": // notif var params DidCloseTextDocumentParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.server.DidClose(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := server.DidClose(ctx, ¶ms) + return true, reply(ctx, nil, err) case "textDocument/didSave": // notif var params DidSaveTextDocumentParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.server.DidSave(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := server.DidSave(ctx, ¶ms) + return true, reply(ctx, nil, err) case "textDocument/willSave": // notif var params WillSaveTextDocumentParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.server.WillSave(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := server.WillSave(ctx, ¶ms) + return true, reply(ctx, nil, err) case "workspace/didChangeWatchedFiles": // notif var params DidChangeWatchedFilesParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.server.DidChangeWatchedFiles(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := server.DidChangeWatchedFiles(ctx, ¶ms) + return true, reply(ctx, nil, err) case "$/setTraceNotification": // notif var params SetTraceParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.server.SetTraceNotification(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + err := server.SetTraceNotification(ctx, ¶ms) + return true, reply(ctx, nil, err) case "$/logTraceNotification": // notif var params LogTraceParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - if err := h.server.LogTraceNotification(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true + err := server.LogTraceNotification(ctx, ¶ms) + return true, reply(ctx, nil, err) case "textDocument/implementation": // req var params ImplementationParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.Implementation(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.Implementation(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/typeDefinition": // req var params TypeDefinitionParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.TypeDefinition(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.TypeDefinition(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/documentColor": // req var params DocumentColorParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.DocumentColor(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.DocumentColor(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/colorPresentation": // req var params ColorPresentationParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.ColorPresentation(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.ColorPresentation(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/foldingRange": // req var params FoldingRangeParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.FoldingRange(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.FoldingRange(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/declaration": // req var params DeclarationParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.Declaration(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.Declaration(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/selectionRange": // req var params SelectionRangeParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.SelectionRange(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + resp, err := server.SelectionRange(ctx, ¶ms) + return true, reply(ctx, resp, err) + case "textDocument/prepareCallHierarchy": // req + var params CallHierarchyPrepareParams + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.PrepareCallHierarchy(ctx, ¶ms) + return true, reply(ctx, resp, err) + case "callHierarchy/incomingCalls": // req + var params CallHierarchyIncomingCallsParams + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.IncomingCalls(ctx, ¶ms) + return true, reply(ctx, resp, err) + case "callHierarchy/outgoingCalls": // req + var params CallHierarchyOutgoingCallsParams + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.OutgoingCalls(ctx, ¶ms) + return true, reply(ctx, resp, err) case "initialize": // req var params ParamInitialize - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.Initialize(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.Initialize(ctx, ¶ms) + return true, reply(ctx, resp, err) case "shutdown": // req - if r.Params != nil { - r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) - return true - } - err := h.server.Shutdown(ctx) - if err := r.Reply(ctx, nil, err); err != nil { - event.Error(ctx, "", err) + if len(r.Params()) > 0 { + return true, reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)) } - return true + err := server.Shutdown(ctx) + return true, reply(ctx, nil, err) case "textDocument/willSaveWaitUntil": // req var params WillSaveTextDocumentParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.WillSaveWaitUntil(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.WillSaveWaitUntil(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/completion": // req var params CompletionParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.Completion(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.Completion(ctx, ¶ms) + return true, reply(ctx, resp, err) case "completionItem/resolve": // req var params CompletionItem - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.Resolve(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.Resolve(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/hover": // req var params HoverParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.Hover(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.Hover(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/signatureHelp": // req var params SignatureHelpParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.SignatureHelp(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.SignatureHelp(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/definition": // req var params DefinitionParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.Definition(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.Definition(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/references": // req var params ReferenceParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.References(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.References(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/documentHighlight": // req var params DocumentHighlightParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.DocumentHighlight(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.DocumentHighlight(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/documentSymbol": // req var params DocumentSymbolParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.DocumentSymbol(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.DocumentSymbol(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/codeAction": // req var params CodeActionParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.CodeAction(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.CodeAction(ctx, ¶ms) + return true, reply(ctx, resp, err) case "workspace/symbol": // req var params WorkspaceSymbolParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.Symbol(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.Symbol(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/codeLens": // req var params CodeLensParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.CodeLens(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.CodeLens(ctx, ¶ms) + return true, reply(ctx, resp, err) case "codeLens/resolve": // req var params CodeLens - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.ResolveCodeLens(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.ResolveCodeLens(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/documentLink": // req var params DocumentLinkParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.DocumentLink(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.DocumentLink(ctx, ¶ms) + return true, reply(ctx, resp, err) case "documentLink/resolve": // req var params DocumentLink - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.ResolveDocumentLink(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.ResolveDocumentLink(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/formatting": // req var params DocumentFormattingParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.Formatting(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.Formatting(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/rangeFormatting": // req var params DocumentRangeFormattingParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.RangeFormatting(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.RangeFormatting(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/onTypeFormatting": // req var params DocumentOnTypeFormattingParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.OnTypeFormatting(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.OnTypeFormatting(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/rename": // req var params RenameParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.Rename(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.Rename(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/prepareRename": // req var params PrepareRenameParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.PrepareRename(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.PrepareRename(ctx, ¶ms) + return true, reply(ctx, resp, err) case "workspace/executeCommand": // req var params ExecuteCommandParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.ExecuteCommand(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true - case "textDocument/prepareCallHierarchy": // req - var params CallHierarchyPrepareParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.PrepareCallHierarchy(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true - case "callHierarchy/incomingCalls": // req - var params CallHierarchyIncomingCallsParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.IncomingCalls(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true - case "callHierarchy/outgoingCalls": // req - var params CallHierarchyOutgoingCallsParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.OutgoingCalls(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.ExecuteCommand(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/semanticTokens": // req var params SemanticTokensParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.SemanticTokens(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.SemanticTokens(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/semanticTokens/edits": // req var params SemanticTokensEditsParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - resp, err := h.server.SemanticTokensEdits(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true + resp, err := server.SemanticTokensEdits(ctx, ¶ms) + return true, reply(ctx, resp, err) case "textDocument/semanticTokens/range": // req var params SemanticTokensRangeParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.SemanticTokensRange(ctx, ¶ms) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true - default: - var params interface{} - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.NonstandardRequest(ctx, r.Method, params) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true + resp, err := server.SemanticTokensRange(ctx, ¶ms) + return true, reply(ctx, resp, err) + default: + return false, nil } } -type serverDispatcher struct { - *jsonrpc2.Conn -} - func (s *serverDispatcher) DidChangeWorkspaceFolders(ctx context.Context, params *DidChangeWorkspaceFoldersParams) error { return s.Conn.Notify(ctx, "workspace/didChangeWorkspaceFolders", params) } @@ -671,7 +467,7 @@ func (s *serverDispatcher) LogTraceNotification(ctx context.Context, params *Log } func (s *serverDispatcher) Implementation(ctx context.Context, params *ImplementationParams) (Definition /*Definition | DefinitionLink[] | null*/, error) { var result Definition /*Definition | DefinitionLink[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/implementation", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/implementation", params, &result); err != nil { return nil, err } return result, nil @@ -679,7 +475,7 @@ func (s *serverDispatcher) Implementation(ctx context.Context, params *Implement func (s *serverDispatcher) TypeDefinition(ctx context.Context, params *TypeDefinitionParams) (Definition /*Definition | DefinitionLink[] | null*/, error) { var result Definition /*Definition | DefinitionLink[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/typeDefinition", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/typeDefinition", params, &result); err != nil { return nil, err } return result, nil @@ -687,7 +483,7 @@ func (s *serverDispatcher) TypeDefinition(ctx context.Context, params *TypeDefin func (s *serverDispatcher) DocumentColor(ctx context.Context, params *DocumentColorParams) ([]ColorInformation, error) { var result []ColorInformation - if err := s.Conn.Call(ctx, "textDocument/documentColor", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/documentColor", params, &result); err != nil { return nil, err } return result, nil @@ -695,7 +491,7 @@ func (s *serverDispatcher) DocumentColor(ctx context.Context, params *DocumentCo func (s *serverDispatcher) ColorPresentation(ctx context.Context, params *ColorPresentationParams) ([]ColorPresentation, error) { var result []ColorPresentation - if err := s.Conn.Call(ctx, "textDocument/colorPresentation", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/colorPresentation", params, &result); err != nil { return nil, err } return result, nil @@ -703,7 +499,7 @@ func (s *serverDispatcher) ColorPresentation(ctx context.Context, params *ColorP func (s *serverDispatcher) FoldingRange(ctx context.Context, params *FoldingRangeParams) ([]FoldingRange /*FoldingRange[] | null*/, error) { var result []FoldingRange /*FoldingRange[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/foldingRange", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/foldingRange", params, &result); err != nil { return nil, err } return result, nil @@ -711,7 +507,7 @@ func (s *serverDispatcher) FoldingRange(ctx context.Context, params *FoldingRang func (s *serverDispatcher) Declaration(ctx context.Context, params *DeclarationParams) (Declaration /*Declaration | DeclarationLink[] | null*/, error) { var result Declaration /*Declaration | DeclarationLink[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/declaration", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/declaration", params, &result); err != nil { return nil, err } return result, nil @@ -719,7 +515,31 @@ func (s *serverDispatcher) Declaration(ctx context.Context, params *DeclarationP func (s *serverDispatcher) SelectionRange(ctx context.Context, params *SelectionRangeParams) ([]SelectionRange /*SelectionRange[] | null*/, error) { var result []SelectionRange /*SelectionRange[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/selectionRange", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/selectionRange", params, &result); err != nil { + return nil, err + } + return result, nil +} + +func (s *serverDispatcher) PrepareCallHierarchy(ctx context.Context, params *CallHierarchyPrepareParams) ([]CallHierarchyItem /*CallHierarchyItem[] | null*/, error) { + var result []CallHierarchyItem /*CallHierarchyItem[] | null*/ + if err := Call(ctx, s.Conn, "textDocument/prepareCallHierarchy", params, &result); err != nil { + return nil, err + } + return result, nil +} + +func (s *serverDispatcher) IncomingCalls(ctx context.Context, params *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/, error) { + var result []CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/ + if err := Call(ctx, s.Conn, "callHierarchy/incomingCalls", params, &result); err != nil { + return nil, err + } + return result, nil +} + +func (s *serverDispatcher) OutgoingCalls(ctx context.Context, params *CallHierarchyOutgoingCallsParams) ([]CallHierarchyOutgoingCall /*CallHierarchyOutgoingCall[] | null*/, error) { + var result []CallHierarchyOutgoingCall /*CallHierarchyOutgoingCall[] | null*/ + if err := Call(ctx, s.Conn, "callHierarchy/outgoingCalls", params, &result); err != nil { return nil, err } return result, nil @@ -727,19 +547,19 @@ func (s *serverDispatcher) SelectionRange(ctx context.Context, params *Selection func (s *serverDispatcher) Initialize(ctx context.Context, params *ParamInitialize) (*InitializeResult, error) { var result *InitializeResult - if err := s.Conn.Call(ctx, "initialize", params, &result); err != nil { + if err := Call(ctx, s.Conn, "initialize", params, &result); err != nil { return nil, err } return result, nil } func (s *serverDispatcher) Shutdown(ctx context.Context) error { - return s.Conn.Call(ctx, "shutdown", nil, nil) + return Call(ctx, s.Conn, "shutdown", nil, nil) } func (s *serverDispatcher) WillSaveWaitUntil(ctx context.Context, params *WillSaveTextDocumentParams) ([]TextEdit /*TextEdit[] | null*/, error) { var result []TextEdit /*TextEdit[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/willSaveWaitUntil", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/willSaveWaitUntil", params, &result); err != nil { return nil, err } return result, nil @@ -747,7 +567,7 @@ func (s *serverDispatcher) WillSaveWaitUntil(ctx context.Context, params *WillSa func (s *serverDispatcher) Completion(ctx context.Context, params *CompletionParams) (*CompletionList /*CompletionItem[] | CompletionList | null*/, error) { var result *CompletionList /*CompletionItem[] | CompletionList | null*/ - if err := s.Conn.Call(ctx, "textDocument/completion", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/completion", params, &result); err != nil { return nil, err } return result, nil @@ -755,7 +575,7 @@ func (s *serverDispatcher) Completion(ctx context.Context, params *CompletionPar func (s *serverDispatcher) Resolve(ctx context.Context, params *CompletionItem) (*CompletionItem, error) { var result *CompletionItem - if err := s.Conn.Call(ctx, "completionItem/resolve", params, &result); err != nil { + if err := Call(ctx, s.Conn, "completionItem/resolve", params, &result); err != nil { return nil, err } return result, nil @@ -763,7 +583,7 @@ func (s *serverDispatcher) Resolve(ctx context.Context, params *CompletionItem) func (s *serverDispatcher) Hover(ctx context.Context, params *HoverParams) (*Hover /*Hover | null*/, error) { var result *Hover /*Hover | null*/ - if err := s.Conn.Call(ctx, "textDocument/hover", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/hover", params, &result); err != nil { return nil, err } return result, nil @@ -771,7 +591,7 @@ func (s *serverDispatcher) Hover(ctx context.Context, params *HoverParams) (*Hov func (s *serverDispatcher) SignatureHelp(ctx context.Context, params *SignatureHelpParams) (*SignatureHelp /*SignatureHelp | null*/, error) { var result *SignatureHelp /*SignatureHelp | null*/ - if err := s.Conn.Call(ctx, "textDocument/signatureHelp", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/signatureHelp", params, &result); err != nil { return nil, err } return result, nil @@ -779,7 +599,7 @@ func (s *serverDispatcher) SignatureHelp(ctx context.Context, params *SignatureH func (s *serverDispatcher) Definition(ctx context.Context, params *DefinitionParams) (Definition /*Definition | DefinitionLink[] | null*/, error) { var result Definition /*Definition | DefinitionLink[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/definition", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/definition", params, &result); err != nil { return nil, err } return result, nil @@ -787,7 +607,7 @@ func (s *serverDispatcher) Definition(ctx context.Context, params *DefinitionPar func (s *serverDispatcher) References(ctx context.Context, params *ReferenceParams) ([]Location /*Location[] | null*/, error) { var result []Location /*Location[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/references", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/references", params, &result); err != nil { return nil, err } return result, nil @@ -795,7 +615,7 @@ func (s *serverDispatcher) References(ctx context.Context, params *ReferencePara func (s *serverDispatcher) DocumentHighlight(ctx context.Context, params *DocumentHighlightParams) ([]DocumentHighlight /*DocumentHighlight[] | null*/, error) { var result []DocumentHighlight /*DocumentHighlight[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/documentHighlight", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/documentHighlight", params, &result); err != nil { return nil, err } return result, nil @@ -803,7 +623,7 @@ func (s *serverDispatcher) DocumentHighlight(ctx context.Context, params *Docume func (s *serverDispatcher) DocumentSymbol(ctx context.Context, params *DocumentSymbolParams) ([]interface{} /*SymbolInformation[] | DocumentSymbol[] | null*/, error) { var result []interface{} /*SymbolInformation[] | DocumentSymbol[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/documentSymbol", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/documentSymbol", params, &result); err != nil { return nil, err } return result, nil @@ -811,7 +631,7 @@ func (s *serverDispatcher) DocumentSymbol(ctx context.Context, params *DocumentS func (s *serverDispatcher) CodeAction(ctx context.Context, params *CodeActionParams) ([]CodeAction /*(Command | CodeAction)[] | null*/, error) { var result []CodeAction /*(Command | CodeAction)[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/codeAction", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/codeAction", params, &result); err != nil { return nil, err } return result, nil @@ -819,7 +639,7 @@ func (s *serverDispatcher) CodeAction(ctx context.Context, params *CodeActionPar func (s *serverDispatcher) Symbol(ctx context.Context, params *WorkspaceSymbolParams) ([]SymbolInformation /*SymbolInformation[] | null*/, error) { var result []SymbolInformation /*SymbolInformation[] | null*/ - if err := s.Conn.Call(ctx, "workspace/symbol", params, &result); err != nil { + if err := Call(ctx, s.Conn, "workspace/symbol", params, &result); err != nil { return nil, err } return result, nil @@ -827,7 +647,7 @@ func (s *serverDispatcher) Symbol(ctx context.Context, params *WorkspaceSymbolPa func (s *serverDispatcher) CodeLens(ctx context.Context, params *CodeLensParams) ([]CodeLens /*CodeLens[] | null*/, error) { var result []CodeLens /*CodeLens[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/codeLens", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/codeLens", params, &result); err != nil { return nil, err } return result, nil @@ -835,7 +655,7 @@ func (s *serverDispatcher) CodeLens(ctx context.Context, params *CodeLensParams) func (s *serverDispatcher) ResolveCodeLens(ctx context.Context, params *CodeLens) (*CodeLens, error) { var result *CodeLens - if err := s.Conn.Call(ctx, "codeLens/resolve", params, &result); err != nil { + if err := Call(ctx, s.Conn, "codeLens/resolve", params, &result); err != nil { return nil, err } return result, nil @@ -843,7 +663,7 @@ func (s *serverDispatcher) ResolveCodeLens(ctx context.Context, params *CodeLens func (s *serverDispatcher) DocumentLink(ctx context.Context, params *DocumentLinkParams) ([]DocumentLink /*DocumentLink[] | null*/, error) { var result []DocumentLink /*DocumentLink[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/documentLink", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/documentLink", params, &result); err != nil { return nil, err } return result, nil @@ -851,7 +671,7 @@ func (s *serverDispatcher) DocumentLink(ctx context.Context, params *DocumentLin func (s *serverDispatcher) ResolveDocumentLink(ctx context.Context, params *DocumentLink) (*DocumentLink, error) { var result *DocumentLink - if err := s.Conn.Call(ctx, "documentLink/resolve", params, &result); err != nil { + if err := Call(ctx, s.Conn, "documentLink/resolve", params, &result); err != nil { return nil, err } return result, nil @@ -859,7 +679,7 @@ func (s *serverDispatcher) ResolveDocumentLink(ctx context.Context, params *Docu func (s *serverDispatcher) Formatting(ctx context.Context, params *DocumentFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error) { var result []TextEdit /*TextEdit[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/formatting", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/formatting", params, &result); err != nil { return nil, err } return result, nil @@ -867,7 +687,7 @@ func (s *serverDispatcher) Formatting(ctx context.Context, params *DocumentForma func (s *serverDispatcher) RangeFormatting(ctx context.Context, params *DocumentRangeFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error) { var result []TextEdit /*TextEdit[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/rangeFormatting", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/rangeFormatting", params, &result); err != nil { return nil, err } return result, nil @@ -875,7 +695,7 @@ func (s *serverDispatcher) RangeFormatting(ctx context.Context, params *Document func (s *serverDispatcher) OnTypeFormatting(ctx context.Context, params *DocumentOnTypeFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error) { var result []TextEdit /*TextEdit[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/onTypeFormatting", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/onTypeFormatting", params, &result); err != nil { return nil, err } return result, nil @@ -883,7 +703,7 @@ func (s *serverDispatcher) OnTypeFormatting(ctx context.Context, params *Documen func (s *serverDispatcher) Rename(ctx context.Context, params *RenameParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error) { var result *WorkspaceEdit /*WorkspaceEdit | null*/ - if err := s.Conn.Call(ctx, "textDocument/rename", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/rename", params, &result); err != nil { return nil, err } return result, nil @@ -891,7 +711,7 @@ func (s *serverDispatcher) Rename(ctx context.Context, params *RenameParams) (*W func (s *serverDispatcher) PrepareRename(ctx context.Context, params *PrepareRenameParams) (*Range /*Range | { range: Range, placeholder: string } | null*/, error) { var result *Range /*Range | { range: Range, placeholder: string } | null*/ - if err := s.Conn.Call(ctx, "textDocument/prepareRename", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/prepareRename", params, &result); err != nil { return nil, err } return result, nil @@ -899,31 +719,7 @@ func (s *serverDispatcher) PrepareRename(ctx context.Context, params *PrepareRen func (s *serverDispatcher) ExecuteCommand(ctx context.Context, params *ExecuteCommandParams) (interface{} /*any | null*/, error) { var result interface{} /*any | null*/ - if err := s.Conn.Call(ctx, "workspace/executeCommand", params, &result); err != nil { - return nil, err - } - return result, nil -} - -func (s *serverDispatcher) PrepareCallHierarchy(ctx context.Context, params *CallHierarchyPrepareParams) ([]CallHierarchyItem /*CallHierarchyItem[] | null*/, error) { - var result []CallHierarchyItem /*CallHierarchyItem[] | null*/ - if err := s.Conn.Call(ctx, "textDocument/prepareCallHierarchy", params, &result); err != nil { - return nil, err - } - return result, nil -} - -func (s *serverDispatcher) IncomingCalls(ctx context.Context, params *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/, error) { - var result []CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/ - if err := s.Conn.Call(ctx, "callHierarchy/incomingCalls", params, &result); err != nil { - return nil, err - } - return result, nil -} - -func (s *serverDispatcher) OutgoingCalls(ctx context.Context, params *CallHierarchyOutgoingCallsParams) ([]CallHierarchyOutgoingCall /*CallHierarchyOutgoingCall[] | null*/, error) { - var result []CallHierarchyOutgoingCall /*CallHierarchyOutgoingCall[] | null*/ - if err := s.Conn.Call(ctx, "callHierarchy/outgoingCalls", params, &result); err != nil { + if err := Call(ctx, s.Conn, "workspace/executeCommand", params, &result); err != nil { return nil, err } return result, nil @@ -931,7 +727,7 @@ func (s *serverDispatcher) OutgoingCalls(ctx context.Context, params *CallHierar func (s *serverDispatcher) SemanticTokens(ctx context.Context, params *SemanticTokensParams) (*SemanticTokens /*SemanticTokens | null*/, error) { var result *SemanticTokens /*SemanticTokens | null*/ - if err := s.Conn.Call(ctx, "textDocument/semanticTokens", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/semanticTokens", params, &result); err != nil { return nil, err } return result, nil @@ -939,7 +735,7 @@ func (s *serverDispatcher) SemanticTokens(ctx context.Context, params *SemanticT func (s *serverDispatcher) SemanticTokensEdits(ctx context.Context, params *SemanticTokensEditsParams) (interface{} /* SemanticTokens | SemanticTokensEdits | nil*/, error) { var result interface{} /* SemanticTokens | SemanticTokensEdits | nil*/ - if err := s.Conn.Call(ctx, "textDocument/semanticTokens/edits", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/semanticTokens/edits", params, &result); err != nil { return nil, err } return result, nil @@ -947,7 +743,7 @@ func (s *serverDispatcher) SemanticTokensEdits(ctx context.Context, params *Sema func (s *serverDispatcher) SemanticTokensRange(ctx context.Context, params *SemanticTokensRangeParams) (*SemanticTokens /*SemanticTokens | null*/, error) { var result *SemanticTokens /*SemanticTokens | null*/ - if err := s.Conn.Call(ctx, "textDocument/semanticTokens/range", params, &result); err != nil { + if err := Call(ctx, s.Conn, "textDocument/semanticTokens/range", params, &result); err != nil { return nil, err } return result, nil @@ -955,7 +751,7 @@ func (s *serverDispatcher) SemanticTokensRange(ctx context.Context, params *Sema func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { var result interface{} - if err := s.Conn.Call(ctx, method, params, &result); err != nil { + if err := Call(ctx, s.Conn, method, params, &result); err != nil { return nil, err } return result, nil diff --git a/internal/vendored/go-tools/lsp/protocol/typescript/code.ts b/internal/vendored/go-tools/lsp/protocol/typescript/code.ts index 550cb090..d196de4c 100644 --- a/internal/vendored/go-tools/lsp/protocol/typescript/code.ts +++ b/internal/vendored/go-tools/lsp/protocol/typescript/code.ts @@ -196,7 +196,8 @@ function genTypes(node: ts.Node) { if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) || ts.isImportDeclaration(node) || ts.isVariableStatement(node) || ts.isExportDeclaration(node) || ts.isEmptyStatement(node) || - node.kind == ts.SyntaxKind.EndOfFileToken) { + ts.isExportAssignment(node) || ts.isImportEqualsDeclaration(node) || + ts.isBlock(node) || node.kind == ts.SyntaxKind.EndOfFileToken) { return; } if (ts.isInterfaceDeclaration(node)) { @@ -214,7 +215,7 @@ function genTypes(node: ts.Node) { // and InitializeResult: [custom: string]: any;] return } else - throw new Error(`unexpected ${strKind(t)}`) + throw new Error(`217 unexpected ${strKind(t)}`) }; v.members.forEach(f); if (mems.length == 0 && !v.heritageClauses && @@ -334,7 +335,7 @@ function genTypes(node: ts.Node) { throw new Error(`Class dup ${loc(c.me)} and ${loc(data.get(c.name).me)}`); data.set(c.name, c); } else { - throw new Error(`unexpected ${strKind(node)} ${loc(node)} `) + throw new Error(`338 unexpected ${strKind(node)} ${loc(node)} `) } } @@ -347,8 +348,6 @@ function dataMerge(a: Data, b: Data): Data { } const ax = `(${a.statements.length},${a.properties.length})` const bx = `(${b.statements.length},${b.properties.length})` - // console.log(`397 - // ${a.name}${ax}${bx}\n${a.me.getText()}\n${b.me.getText()}\n`) switch (a.name) { case 'InitializeError': case 'MessageType': @@ -358,13 +357,14 @@ function dataMerge(a: Data, b: Data): Data { // want the Module return a.statements.length > 0 ? a : b; case 'CancellationToken': + case 'CancellationStrategy': // want the Interface return a.properties.length > 0 ? a : b; case 'TextDocumentContentChangeEvent': // almost the same return a; } console.log( - `${strKind(a.me)} ${strKind(b.me)} ${a.name} ${loc(a.me)} ${loc(b.me)}`) + `367 ${strKind(a.me)} ${strKind(b.me)} ${a.name} ${loc(a.me)} ${loc(b.me)}`) throw new Error(`Fix dataMerge for ${a.name}`) } @@ -676,6 +676,7 @@ function goUnionType(n: ts.UnionTypeNode, nm: string): string { if (a == 'BooleanKeyword') { // usually want bool if (nm == 'codeActionProvider') return `interface{} ${help}`; if (nm == 'renameProvider') return `interface{} ${help}`; + if (nm == 'save') return `${goType(n.types[1], '680')} ${help}`; return `${goType(n.types[0], 'b')} ${help}` } if (b == 'ArrayType') return `${goType(n.types[1], 'c')} ${help}`; @@ -687,7 +688,7 @@ function goUnionType(n: ts.UnionTypeNode, nm: string): string { if (a == 'TypeLiteral' && nm == 'TextDocumentContentChangeEvent') { return `${goType(n.types[0], nm)}` } - throw new Error(`724 ${a} ${b} ${n.getText()} ${loc(n)}`); + throw new Error(`691 ${a} ${b} ${n.getText()} ${loc(n)}`); case 3: const aa = strKind(n.types[0]) const bb = strKind(n.types[1]) @@ -726,7 +727,7 @@ function goUnionType(n: ts.UnionTypeNode, nm: string): string { // Result will be interface{} with a comment let isLiteral = true; let literal = 'string'; - let res = `interface{ } /* ` + let res = `interface{} /* ` n.types.forEach((v: ts.TypeNode, i: number) => { // might get an interface inside: // (Command | CodeAction)[] | null @@ -907,9 +908,8 @@ let server: side = { }; // commonly used output -const notNil = `if r.Params != nil { - r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) - return true +const notNil = `if len(r.Params()) > 0 { + return true, reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)) }`; // Go code for notifications. Side is client or server, m is the request @@ -924,19 +924,14 @@ function goNot(side: side, m: string) { let case1 = notNil; if (a != '' && a != 'void') { case1 = `var params ${a} - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.${side.name}.${nm}(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) } - return true`; + err:= ${side.name}.${nm}(ctx, ¶ms) + return true, reply(ctx, nil, err)` } else { - case1 = `if err := h.${side.name}.${nm}(ctx); err != nil { - event.Error(ctx, "", err) - } - return true`; + case1 = `err := ${side.name}.${nm}(ctx) + return true, reply(ctx, nil, err)`; } side.cases.push(`${caseHdr}\n${case1}`); @@ -965,45 +960,38 @@ function goReq(side: side, m: string) { if (a != '') { if (extraTypes.has('Param' + nm)) a = 'Param' + nm case1 = `var params ${a} - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) }`; } const arg2 = a == '' ? '' : ', ¶ms'; - let case2 = `if err := h.${side.name}.${nm}(ctx${arg2}); err != nil { + let case2 = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil { event.Error(ctx, "", err) }`; if (b != '' && b != 'void') { - case2 = `resp, err := h.${side.name}.${nm}(ctx${arg2}) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true`; + case2 = `resp, err := ${side.name}.${nm}(ctx${arg2}) + return true, reply(ctx, resp, err)`; } else { // response is nil - case2 = `err := h.${side.name}.${nm}(ctx${arg2}) - if err := r.Reply(ctx, nil, err); err != nil { - event.Error(ctx, "", err) - } - return true` + case2 = `err := ${side.name}.${nm}(ctx${arg2}) + return true, reply(ctx, nil, err)` } side.cases.push(`${caseHdr}\n${case1}\n${case2}`); const callHdr = `func (s *${side.name}Dispatcher) ${sig(nm, a, b, true)} {`; - let callBody = `return s.Conn.Call(ctx, "${m}", nil, nil)\n}`; + let callBody = `return Call(ctx, s.Conn, "${m}", nil, nil)\n}`; if (b != '' && b != 'void') { const p2 = a == '' ? 'nil' : 'params'; const returnType = indirect(b) ? `*${b}` : b; callBody = `var result ${returnType} - if err := s.Conn.Call(ctx, "${m}", ${ + if err := Call(ctx, s.Conn, "${m}", ${ p2}, &result); err != nil { return nil, err } return result, nil }`; } else if (a != '') { - callBody = `return s.Conn.Call(ctx, "${m}", params, nil) // Call, not Notify + callBody = `return Call(ctx, s.Conn, "${m}", params, nil) // Call, not Notify }` } side.calls.push(`${callHdr}\n${callBody}\n`); @@ -1085,36 +1073,23 @@ function output(side: side) { import ( "context" "encoding/json" + "fmt" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/xcontext" ) `); const a = side.name[0].toUpperCase() + side.name.substring(1) f(`type ${a} interface {`); side.methods.forEach((v) => {f(v)}); f('}\n'); - f(`func (h ${ - side.name}Handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool { - if delivered { - return false - } - if ctx.Err() != nil { - ctx := xcontext.Detach(ctx) - r.Reply(ctx, nil, jsonrpc2.NewErrorf(RequestCancelledError, "")) - return true - } - switch r.Method {`); + f(`func ${side.name}Dispatch(ctx context.Context, ${side.name} ${a}, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { + switch r.Method() {`); side.cases.forEach((v) => {f(v)}); f(` + default: + return false, nil } }`); - f(` - type ${side.name}Dispatcher struct { - *jsonrpc2.Conn - } - `); side.calls.forEach((v) => {f(v)}); } @@ -1125,26 +1100,12 @@ function nonstandardRequests() { server.calls.push( `func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { var result interface{} - if err := s.Conn.Call(ctx, method, params, &result); err != nil { + if err := Call(ctx, s.Conn, method, params, &result); err != nil { return nil, err } return result, nil } `) - client.cases.push(`default: - return false`) - server.cases.push(`default: - var params interface{} - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - resp, err := h.server.NonstandardRequest(ctx, r.Method, params) - if err := r.Reply(ctx, resp, err); err != nil { - event.Error(ctx, "", err) - } - return true -`) } // ----- remember it's a scripting language diff --git a/internal/vendored/go-tools/lsp/protocol/typescript/util.ts b/internal/vendored/go-tools/lsp/protocol/typescript/util.ts index 51d2439d..77fbfede 100644 --- a/internal/vendored/go-tools/lsp/protocol/typescript/util.ts +++ b/internal/vendored/go-tools/lsp/protocol/typescript/util.ts @@ -10,11 +10,11 @@ import * as ts from 'typescript'; let dir = process.env['HOME']; const srcDir = '/vscode-languageserver-node' export const fnames = [ - //`${dir}${srcDir}/protocol/src/protocol.ts`, // why isn't this main.ts? - `${dir}/${srcDir}/protocol/src/main.ts`, `${dir}${srcDir}/types/src/main.ts`, - `${dir}${srcDir}/jsonrpc/src/main.ts` + `${dir}${srcDir}/protocol/src/common/protocol.ts`, + `${dir}/${srcDir}/protocol/src/browser/main.ts`, `${dir}${srcDir}/types/src/main.ts`, + `${dir}${srcDir}/jsonrpc/src/node/main.ts` ]; -export const gitHash = '151b520c995ee3d76729b5c46258ab273d989726' +export const gitHash = '1f688e2f65f3a6fc9ba395380cd7b059667a9ecf' let outFname = 'tsprotocol.go'; let fda: number, fdb: number, fde: number; // file descriptors @@ -66,10 +66,10 @@ export function computeHeader(pkgDoc: boolean): string { } } const a = - `// Package protocol contains data types and code for LSP jsonrpcs\n` + - `// generated automatically from vscode-languageserver-node\n` + - `// commit: ${gitHash}\n` + - `// last fetched ${lastDate}\n` + `// Package protocol contains data types and code for LSP jsonrpcs\n` + + `// generated automatically from vscode-languageserver-node\n` + + `// commit: ${gitHash}\n` + + `// last fetched ${lastDate}\n` const b = 'package protocol\n' const c = `\n// Code generated (see typescript/README.md) DO NOT EDIT.\n\n` if (pkgDoc) { @@ -95,7 +95,7 @@ export function goName(s: string): string { // Generate JSON tag for a struct field export function JSON(n: ts.PropertySignature): string { const json = `\`json:"${n.name.getText()}${ - n.questionToken != undefined ? ',omitempty' : ''}"\``; + n.questionToken != undefined ? ',omitempty' : ''}"\``; return json } @@ -114,7 +114,7 @@ export function constName(nm: string, type: string): string { let ans = nm; if (pref.get(type)) ans = pref.get(type) + ans; if (suff.has(type)) ans = ans + suff.get(type) - return ans + return ans } // Find the comments associated with an AST node @@ -193,7 +193,7 @@ export function loc(node: ts.Node): string { const n = fn.search(/-node./) fn = fn.substring(n + 6) return `${fn} ${x.line + 1}: ${x.character + 1} (${y.line + 1}: ${ - y.character + 1})` + y.character + 1})` } // --- various string stuff @@ -201,7 +201,7 @@ export function loc(node: ts.Node): string { // as part of printing the AST tree function kinds(n: ts.Node): string { let res = 'Seen ' + strKind(n); - function f(n: ts.Node): void{res += ' ' + strKind(n)}; + function f(n: ts.Node): void {res += ' ' + strKind(n)}; ts.forEachChild(n, f) return res } diff --git a/internal/vendored/go-tools/stack/gostacks/gostacks.go b/internal/vendored/go-tools/stack/gostacks/gostacks.go new file mode 100644 index 00000000..27b19ab5 --- /dev/null +++ b/internal/vendored/go-tools/stack/gostacks/gostacks.go @@ -0,0 +1,23 @@ +// 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. + +// The gostacks command processes stdin looking for things that look like +// stack traces and simplifying them to make the log more readable. +// It collates stack traces that have the same path as well as simplifying the +// individual lines of the trace. +// The processed log is printed to stdout. +package main + +import ( + "fmt" + "os" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/stack" +) + +func main() { + if err := stack.Process(os.Stdout, os.Stdin); err != nil { + fmt.Fprintln(os.Stderr, err) + } +} diff --git a/internal/vendored/go-tools/stack/parse.go b/internal/vendored/go-tools/stack/parse.go new file mode 100644 index 00000000..e01da8f0 --- /dev/null +++ b/internal/vendored/go-tools/stack/parse.go @@ -0,0 +1,175 @@ +// 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 stack + +import ( + "bufio" + "errors" + "io" + "regexp" + "strconv" +) + +var ( + reBlank = regexp.MustCompile(`^\s*$`) + reGoroutine = regexp.MustCompile(`^\s*goroutine (\d+) \[([^\]]*)\]:\s*$`) + reCall = regexp.MustCompile(`^\s*` + + `(created by )?` + //marker + `(([\w/.]+/)?[\w]+)\.` + //package + `(\(([^:.)]*)\)\.)?` + //optional type + `([\w\.]+)` + //function + `(\(.*\))?` + // args + `\s*$`) + rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`) + + errBreakParse = errors.New("break parse") +) + +// Scanner splits an input stream into lines in a way that is consumable by +// the parser. +type Scanner struct { + lines *bufio.Scanner + done bool +} + +// NewScanner creates a scanner on top of a reader. +func NewScanner(r io.Reader) *Scanner { + s := &Scanner{ + lines: bufio.NewScanner(r), + } + s.Skip() // prefill + return s +} + +// Peek returns the next line without consuming it. +func (s *Scanner) Peek() string { + if s.done { + return "" + } + return s.lines.Text() +} + +// Skip consumes the next line without looking at it. +// Normally used after it has already been looked at using Peek. +func (s *Scanner) Skip() { + if !s.lines.Scan() { + s.done = true + } +} + +// Next consumes and returns the next line. +func (s *Scanner) Next() string { + line := s.Peek() + s.Skip() + return line +} + +// Done returns true if the scanner has reached the end of the underlying +// stream. +func (s *Scanner) Done() bool { + return s.done +} + +// Err returns true if the scanner has reached the end of the underlying +// stream. +func (s *Scanner) Err() error { + return s.lines.Err() +} + +// Match returns the submatchs of the regular expression against the next line. +// If it matched the line is also consumed. +func (s *Scanner) Match(re *regexp.Regexp) []string { + if s.done { + return nil + } + match := re.FindStringSubmatch(s.Peek()) + if match != nil { + s.Skip() + } + return match +} + +// SkipBlank skips any number of pure whitespace lines. +func (s *Scanner) SkipBlank() { + for !s.done { + line := s.Peek() + if len(line) != 0 && !reBlank.MatchString(line) { + return + } + s.Skip() + } +} + +// Parse the current contiguous block of goroutine stack traces until the +// scanned content no longer matches. +func Parse(scanner *Scanner) (Dump, error) { + dump := Dump{} + for { + gr, ok := parseGoroutine(scanner) + if !ok { + return dump, nil + } + dump = append(dump, gr) + } +} + +func parseGoroutine(scanner *Scanner) (Goroutine, bool) { + match := scanner.Match(reGoroutine) + if match == nil { + return Goroutine{}, false + } + id, _ := strconv.ParseInt(match[1], 0, 32) + gr := Goroutine{ + ID: int(id), + State: match[2], + } + for { + frame, ok := parseFrame(scanner) + if !ok { + scanner.SkipBlank() + return gr, true + } + if frame.Position.Filename != "" { + gr.Stack = append(gr.Stack, frame) + } + } +} + +func parseFrame(scanner *Scanner) (Frame, bool) { + fun, ok := parseFunction(scanner) + if !ok { + return Frame{}, false + } + frame := Frame{ + Function: fun, + } + frame.Position, ok = parsePosition(scanner) + // if ok is false, then this is a broken state. + // we got the func but not the file that must follow + // the consumed line can be recovered from the frame + //TODO: push back the fun raw + return frame, ok +} + +func parseFunction(scanner *Scanner) (Function, bool) { + match := scanner.Match(reCall) + if match == nil { + return Function{}, false + } + return Function{ + Package: match[2], + Type: match[5], + Name: match[6], + }, true +} + +func parsePosition(scanner *Scanner) (Position, bool) { + match := scanner.Match(rePos) + if match == nil { + return Position{}, false + } + line, _ := strconv.ParseInt(match[2], 0, 32) + return Position{Filename: match[1], Line: int(line)}, true +} diff --git a/internal/vendored/go-tools/stack/process.go b/internal/vendored/go-tools/stack/process.go new file mode 100644 index 00000000..ac193666 --- /dev/null +++ b/internal/vendored/go-tools/stack/process.go @@ -0,0 +1,112 @@ +// 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 stack + +import ( + "bytes" + "fmt" + "io" + "runtime" + "sort" +) + +// Capture get the current stack traces from the runtime. +func Capture() Dump { + buf := make([]byte, 2<<20) + buf = buf[:runtime.Stack(buf, true)] + scanner := NewScanner(bytes.NewReader(buf)) + dump, _ := Parse(scanner) + return dump +} + +// Summarize a dump for easier consumption. +// This collates goroutines with equivalent stacks. +func Summarize(dump Dump) Summary { + s := Summary{ + Total: len(dump), + } + for _, gr := range dump { + s.addGoroutine(gr) + } + return s +} + +// Process and input stream to an output stream, summarizing any stacks that +// are detected in place. +func Process(out io.Writer, in io.Reader) error { + scanner := NewScanner(in) + for { + dump, err := Parse(scanner) + summary := Summarize(dump) + switch { + case len(dump) > 0: + fmt.Fprintf(out, "%+v\n\n", summary) + case err != nil: + return err + case scanner.Done(): + return scanner.Err() + default: + // must have been a line that is not part of a dump + fmt.Fprintln(out, scanner.Next()) + } + } +} + +// Diff calculates the delta between two dumps. +func Diff(before, after Dump) Delta { + result := Delta{} + processed := make(map[int]bool) + for _, gr := range before { + processed[gr.ID] = false + } + for _, gr := range after { + if _, found := processed[gr.ID]; found { + result.Shared = append(result.Shared, gr) + } else { + result.After = append(result.After, gr) + } + processed[gr.ID] = true + } + for _, gr := range before { + if done := processed[gr.ID]; !done { + result.Before = append(result.Before, gr) + } + } + return result +} + +// TODO: do we want to allow contraction of stacks before comparison? +func (s *Summary) addGoroutine(gr Goroutine) { + index := sort.Search(len(s.Calls), func(i int) bool { + return !s.Calls[i].Stack.less(gr.Stack) + }) + if index >= len(s.Calls) || !s.Calls[index].Stack.equal(gr.Stack) { + // insert new stack, first increase the length + s.Calls = append(s.Calls, Call{}) + // move the top part upward to make space + copy(s.Calls[index+1:], s.Calls[index:]) + // insert the new call + s.Calls[index] = Call{ + Stack: gr.Stack, + } + } + // merge the goroutine into the matched call + s.Calls[index].merge(gr) +} + +//TODO: do we want other grouping strategies? +func (c *Call) merge(gr Goroutine) { + for i := range c.Groups { + canditate := &c.Groups[i] + if canditate.State == gr.State { + canditate.Goroutines = append(canditate.Goroutines, gr) + return + } + } + c.Groups = append(c.Groups, Group{ + State: gr.State, + Goroutines: []Goroutine{gr}, + }) +} diff --git a/internal/vendored/go-tools/stack/stack.go b/internal/vendored/go-tools/stack/stack.go new file mode 100644 index 00000000..479301a7 --- /dev/null +++ b/internal/vendored/go-tools/stack/stack.go @@ -0,0 +1,170 @@ +// 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 stack provides support for parsing standard goroutine stack traces. +package stack + +import ( + "fmt" + "text/tabwriter" +) + +// Dump is a raw set of goroutines and their stacks. +type Dump []Goroutine + +// Goroutine is a single parsed goroutine dump. +type Goroutine struct { + State string // state that the goroutine is in. + ID int // id of the goroutine. + Stack Stack // call frames that make up the stack +} + +// Stack is a set of frames in a callstack. +type Stack []Frame + +// Frame is a point in a call stack. +type Frame struct { + Function Function + Position Position +} + +// Function is the function called at a frame. +type Function struct { + Package string // package name of function if known + Type string // if set function is a method of this type + Name string // function name of the frame +} + +// Position is the file position for a frame. +type Position struct { + Filename string // source filename + Line int // line number within file +} + +// Summary is a set of stacks processed and collated into Calls. +type Summary struct { + Total int // the total count of goroutines in the summary + Calls []Call // the collated stack traces +} + +// Call is set of goroutines that all share the same callstack. +// They will be grouped by state. +type Call struct { + Stack Stack // the shared callstack information + Groups []Group // the sets of goroutines with the same state +} + +// Group is a set of goroutines with the same stack that are in the same state. +type Group struct { + State string // the shared state of the goroutines + Goroutines []Goroutine // the set of goroutines in this group +} + +// Delta represents the difference between two stack dumps. +type Delta struct { + Before Dump // The goroutines that were only in the before set. + Shared Dump // The goroutines that were in both sets. + After Dump // The goroutines that were only in the after set. +} + +func (s Stack) equal(other Stack) bool { + if len(s) != len(other) { + return false + } + for i, frame := range s { + if !frame.equal(other[i]) { + return false + } + } + return true +} + +func (s Stack) less(other Stack) bool { + for i, frame := range s { + if i >= len(other) { + return false + } + if frame.less(other[i]) { + return true + } + if !frame.equal(other[i]) { + return false + } + } + return len(s) < len(other) +} + +func (f Frame) equal(other Frame) bool { + return f.Position.equal(other.Position) +} + +func (f Frame) less(other Frame) bool { + return f.Position.less(other.Position) +} + +func (p Position) equal(other Position) bool { + return p.Filename == other.Filename && p.Line == other.Line +} + +func (p Position) less(other Position) bool { + if p.Filename < other.Filename { + return true + } + if p.Filename > other.Filename { + return false + } + return p.Line < other.Line +} + +func (s Summary) Format(w fmt.State, r rune) { + tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) + for i, c := range s.Calls { + if i > 0 { + fmt.Fprintf(tw, "\n\n") + tw.Flush() + } + fmt.Fprint(tw, c) + } + tw.Flush() + if s.Total > 0 && w.Flag('+') { + fmt.Fprintf(w, "\n\n%d goroutines, %d unique", s.Total, len(s.Calls)) + } +} + +func (c Call) Format(w fmt.State, r rune) { + for i, g := range c.Groups { + if i > 0 { + fmt.Fprint(w, " ") + } + fmt.Fprint(w, g) + } + for _, f := range c.Stack { + fmt.Fprintf(w, "\n%v", f) + } +} + +func (g Group) Format(w fmt.State, r rune) { + fmt.Fprintf(w, "[%v]: ", g.State) + for i, gr := range g.Goroutines { + if i > 0 { + fmt.Fprint(w, ", ") + } + fmt.Fprintf(w, "$%d", gr.ID) + } +} + +func (f Frame) Format(w fmt.State, c rune) { + fmt.Fprintf(w, "%v:\t%v", f.Position, f.Function) +} + +func (f Function) Format(w fmt.State, c rune) { + if f.Type != "" { + fmt.Fprintf(w, "(%v).", f.Type) + } + fmt.Fprintf(w, "%v", f.Name) +} + +func (p Position) Format(w fmt.State, c rune) { + fmt.Fprintf(w, "%v:%v", p.Filename, p.Line) +} diff --git a/internal/vendored/go-tools/stack/stack_test.go b/internal/vendored/go-tools/stack/stack_test.go new file mode 100644 index 00000000..860c22ba --- /dev/null +++ b/internal/vendored/go-tools/stack/stack_test.go @@ -0,0 +1,193 @@ +// 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 stack_test + +import ( + "bytes" + "strings" + "testing" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/stack" +) + +func TestProcess(t *testing.T) { + for _, test := range []struct{ name, input, expect string }{{ + name: `empty`, + input: ``, + expect: ``, + }, { + name: `no_frame`, + input: `goroutine 1 [running]:`, + expect: ` +[running]: $1 + +1 goroutines, 1 unique +`, + }, { + name: `one_frame`, + input: ` +goroutine 1 [running]: +package.function(args) + file.go:10 +`, + expect: ` +[running]: $1 +file.go:10: function + +1 goroutines, 1 unique +`, + }, { + name: `one_call`, + input: ` +goroutine 1 [running]: +package1.functionA(args) + file1.go:10 +package2.functionB(args) + file2.go:20 +package3.functionC(args) + file3.go:30 +`, + expect: ` +[running]: $1 +file1.go:10: functionA +file2.go:20: functionB +file3.go:30: functionC + +1 goroutines, 1 unique +`, + }, { + name: `two_call`, + input: ` +goroutine 1 [running]: +package1.functionA(args) + file1.go:10 +goroutine 2 [running]: +package2.functionB(args) + file2.go:20 +`, + expect: ` +[running]: $1 +file1.go:10: functionA + +[running]: $2 +file2.go:20: functionB + +2 goroutines, 2 unique +`, + }, { + name: `merge_call`, + input: ` +goroutine 1 [running]: +package1.functionA(args) + file1.go:10 +goroutine 2 [running]: +package1.functionA(args) + file1.go:10 +`, + expect: ` +[running]: $1, $2 +file1.go:10: functionA + +2 goroutines, 1 unique +`, + }, { + name: `alternating_call`, + input: ` +goroutine 1 [running]: +package1.functionA(args) + file1.go:10 +goroutine 2 [running]: +package2.functionB(args) + file2.go:20 +goroutine 3 [running]: +package1.functionA(args) + file1.go:10 +goroutine 4 [running]: +package2.functionB(args) + file2.go:20 +goroutine 5 [running]: +package1.functionA(args) + file1.go:10 +goroutine 6 [running]: +package2.functionB(args) + file2.go:20 +`, + expect: ` +[running]: $1, $3, $5 +file1.go:10: functionA + +[running]: $2, $4, $6 +file2.go:20: functionB + +6 goroutines, 2 unique +`, + }, { + name: `sort_calls`, + input: ` +goroutine 1 [running]: +package3.functionC(args) + file3.go:30 +goroutine 2 [running]: +package2.functionB(args) + file2.go:20 +goroutine 3 [running]: +package1.functionA(args) + file1.go:10 +`, + expect: ` +[running]: $3 +file1.go:10: functionA + +[running]: $2 +file2.go:20: functionB + +[running]: $1 +file3.go:30: functionC + +3 goroutines, 3 unique +`, + }, { + name: `real_single`, + input: ` +panic: oops + +goroutine 53 [running]: +github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2_test.testHandler.func1(0x1240c20, 0xc000013350, 0xc0000133b0, 0x1240ca0, 0xc00002ab00, 0x3, 0x3) + /work/tools/internal/jsonrpc2/jsonrpc2_test.go:160 +0x74c +github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2.(*Conn).Run(0xc000204330, 0x1240c20, 0xc000204270, 0x1209570, 0xc000212120, 0x1242700) + /work/tools/internal/jsonrpc2/jsonrpc2.go:187 +0x777 +github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2_test.run.func1(0x123ebe0, 0xc000206018, 0x123ec20, 0xc000206010, 0xc0002080a0, 0xc000204330, 0x1240c20, 0xc000204270, 0xc000212120) + /work/tools/internal/jsonrpc2/jsonrpc2_test.go:131 +0xe2 +created by github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2_test.run + /work/tools/internal/jsonrpc2/jsonrpc2_test.go:121 +0x263 +FAIL github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2 0.252s +FAIL +`, + expect: ` +panic: oops + +[running]: $53 +/work/tools/internal/jsonrpc2/jsonrpc2_test.go:160: testHandler.func1 +/work/tools/internal/jsonrpc2/jsonrpc2.go:187: (*Conn).Run +/work/tools/internal/jsonrpc2/jsonrpc2_test.go:131: run.func1 +/work/tools/internal/jsonrpc2/jsonrpc2_test.go:121: run + +1 goroutines, 1 unique + +FAIL github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2 0.252s +FAIL +`, + }} { + t.Run(test.name, func(t *testing.T) { + buf := &bytes.Buffer{} + stack.Process(buf, strings.NewReader(test.input)) + expect := strings.TrimSpace(test.expect) + got := strings.TrimSpace(buf.String()) + if got != expect { + t.Errorf("got:\n%s\nexpect:\n%s", got, expect) + } + }) + } +} diff --git a/internal/vendored/go-tools/stack/stacktest/stacktest.go b/internal/vendored/go-tools/stack/stacktest/stacktest.go new file mode 100644 index 00000000..b2972ff0 --- /dev/null +++ b/internal/vendored/go-tools/stack/stacktest/stacktest.go @@ -0,0 +1,50 @@ +// Copyright 2018 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 stacktest + +import ( + "testing" + "time" + + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/stack" +) + +//this is only needed to support pre 1.14 when testing.TB did not have Cleanup +type withCleanup interface { + Cleanup(func()) +} + +// the maximum amount of time to wait for goroutines to clean themselves up. +const maxWait = time.Second + +// NoLeak checks that a test (or benchmark) does not leak any goroutines. +func NoLeak(t testing.TB) { + c, ok := t.(withCleanup) + if !ok { + return + } + before := stack.Capture() + c.Cleanup(func() { + var delta stack.Delta + start := time.Now() + delay := time.Millisecond + for { + after := stack.Capture() + delta = stack.Diff(before, after) + if len(delta.After) == 0 { + // no leaks + return + } + if time.Since(start) > maxWait { + break + } + time.Sleep(delay) + delay *= 2 + } + // it's been long enough, and leaks are still present + summary := stack.Summarize(delta.After) + t.Errorf("goroutine leak detected:\n%+v", summary) + }) +} diff --git a/internal/vendored/go-tools/telemetry/doc.go b/internal/vendored/go-tools/telemetry/doc.go deleted file mode 100644 index 2e388865..00000000 --- a/internal/vendored/go-tools/telemetry/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -// 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 telemetry provides a set of packages that cover the main -// concepts of telemetry in an implementation agnostic way. -// The interface for libraries that want to expose telemetry is the event -// package. -// As a binary author you might look at exporter for methods of exporting the -// telemetry to external tools. -package telemetry diff --git a/internal/vendored/go-tools/telemetry/event/event.go b/internal/vendored/go-tools/telemetry/event/event.go deleted file mode 100644 index c28e9d99..00000000 --- a/internal/vendored/go-tools/telemetry/event/event.go +++ /dev/null @@ -1,107 +0,0 @@ -// 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 support for event based telemetry. -package event - -import ( - "fmt" - "time" -) - -type eventType uint8 - -const ( - invalidType = eventType(iota) - LogType // an event that should be recorded in a log - StartSpanType // the start of a span of time - EndSpanType // the end of a span of time - LabelType // some values that should be noted for later events - DetachType // an event that causes a context to detach - RecordType // a value that should be tracked -) - -// sTags is used to hold a small number of tags inside an event whichout -// requiring a separate allocation. -// As tags are often on the stack, this avoids 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 tags (one for each value and -// one for the message itself). -type sTags [3]Tag - -// Event holds the information about an event of note that ocurred. -type Event struct { - At time.Time - - typ eventType - static sTags // inline storage for the first few tags - dynamic []Tag // dynamically sized storage for remaining tags -} - -// eventTagMap implements TagMap for a the tags of an Event. -type eventTagMap struct { - event Event -} - -func (ev Event) IsLog() bool { return ev.typ == LogType } -func (ev Event) IsEndSpan() bool { return ev.typ == EndSpanType } -func (ev Event) IsStartSpan() bool { return ev.typ == StartSpanType } -func (ev Event) IsLabel() bool { return ev.typ == LabelType } -func (ev Event) IsDetach() bool { return ev.typ == DetachType } -func (ev Event) IsRecord() bool { return ev.typ == RecordType } - -func (ev Event) Format(f fmt.State, r rune) { - tagMap := ev.Map() - if !ev.At.IsZero() { - fmt.Fprint(f, ev.At.Format("2006/01/02 15:04:05 ")) - } - msg := Msg.Get(tagMap) - err := Err.Get(tagMap) - fmt.Fprint(f, msg) - if err != nil { - if f.Flag('+') { - fmt.Fprintf(f, ": %+v", err) - } else { - fmt.Fprintf(f, ": %v", err) - } - } - for it := ev.Tags(); it.Valid(); it.Advance() { - tag := it.Tag() - fmt.Fprintf(f, "\n\t%v", tag) - } -} - -func (ev Event) Tags() TagIterator { - return ChainTagIterators( - NewTagIterator(ev.static[:]...), - NewTagIterator(ev.dynamic...)) -} - -func (ev Event) Map() TagMap { - return &eventTagMap{event: ev} -} - -func (m *eventTagMap) Find(key interface{}) Tag { - for _, tag := range m.event.static { - if tag.Key == key { - return tag - } - } - for _, tag := range m.event.dynamic { - if tag.Key == key { - return tag - } - } - return Tag{} -} - -func makeEvent(typ eventType, static sTags, tags []Tag) Event { - return Event{ - typ: typ, - static: static, - dynamic: tags, - } -} diff --git a/internal/vendored/go-tools/telemetry/event/key.go b/internal/vendored/go-tools/telemetry/event/key.go deleted file mode 100644 index 97572c37..00000000 --- a/internal/vendored/go-tools/telemetry/event/key.go +++ /dev/null @@ -1,499 +0,0 @@ -// 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 ( - "math" -) - -var ( - // Msg is a key used to add message strings to tag lists. - Msg = NewStringKey("message", "a readable message") - // Name is used for things like traces that have a name. - Name = NewStringKey("name", "an entity name") - // Err is a key used to add error values to tag lists. - Err = NewErrorKey("error", "an error that occurred") -) - -// Key is the interface shared by all key implementations. -type Key interface { - // Name returns the key name. - Name() string - // Description returns a string that can be used to describe the value. - Description() string -} - -// key is used as the identity of a Tag. -// 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 struct { - name string - description string -} - -// ValueKey represents a key for untyped values. -type ValueKey struct { - name string - description string -} - -// NewKey creates a new Key for untyped values. -func NewKey(name, description string) *ValueKey { - return &ValueKey{name: name, description: description} -} - -func (k *ValueKey) Name() string { return k.name } -func (k *ValueKey) Description() string { return k.description } - -// Get can be used to get a tag for the key from a TagMap. -func (k *ValueKey) Get(tags TagMap) interface{} { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return nil -} - -// From can be used to get a value from a Tag. -func (k *ValueKey) From(t Tag) interface{} { return t.untyped } - -// Of creates a new Tag with this key and the supplied value. -func (k *ValueKey) Of(value interface{}) Tag { return Tag{Key: k, untyped: value} } - -// IntKey represents a key -type IntKey struct { - name string - description string -} - -// NewIntKey creates a new Key for int values. -func NewIntKey(name, description string) *IntKey { - return &IntKey{name: name, description: description} -} - -func (k *IntKey) Name() string { return k.name } -func (k *IntKey) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *IntKey) Of(v int) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *IntKey) Get(tags TagMap) int { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *IntKey) From(t Tag) int { return int(t.packed) } - -// Int8Key represents a key -type Int8Key struct { - name string - description string -} - -// NewInt8Key creates a new Key for int8 values. -func NewInt8Key(name, description string) *Int8Key { - return &Int8Key{name: name, description: description} -} - -func (k *Int8Key) Name() string { return k.name } -func (k *Int8Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *Int8Key) Of(v int8) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *Int8Key) Get(tags TagMap) int8 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *Int8Key) From(t Tag) int8 { return int8(t.packed) } - -// Int16Key represents a key -type Int16Key struct { - name string - description string -} - -// NewInt16Key creates a new Key for int16 values. -func NewInt16Key(name, description string) *Int16Key { - return &Int16Key{name: name, description: description} -} - -func (k *Int16Key) Name() string { return k.name } -func (k *Int16Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *Int16Key) Of(v int16) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *Int16Key) Get(tags TagMap) int16 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *Int16Key) From(t Tag) int16 { return int16(t.packed) } - -// Int32Key represents a key -type Int32Key struct { - name string - description string -} - -// NewInt32Key creates a new Key for int32 values. -func NewInt32Key(name, description string) *Int32Key { - return &Int32Key{name: name, description: description} -} - -func (k *Int32Key) Name() string { return k.name } -func (k *Int32Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *Int32Key) Of(v int32) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *Int32Key) Get(tags TagMap) int32 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *Int32Key) From(t Tag) int32 { return int32(t.packed) } - -// Int64Key represents a key -type Int64Key struct { - name string - description string -} - -// NewInt64Key creates a new Key for int64 values. -func NewInt64Key(name, description string) *Int64Key { - return &Int64Key{name: name, description: description} -} - -func (k *Int64Key) Name() string { return k.name } -func (k *Int64Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *Int64Key) Of(v int64) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *Int64Key) Get(tags TagMap) int64 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *Int64Key) From(t Tag) int64 { return int64(t.packed) } - -// UIntKey represents a key -type UIntKey struct { - name string - description string -} - -// NewUIntKey creates a new Key for uint values. -func NewUIntKey(name, description string) *UIntKey { - return &UIntKey{name: name, description: description} -} - -func (k *UIntKey) Name() string { return k.name } -func (k *UIntKey) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *UIntKey) Of(v uint) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *UIntKey) Get(tags TagMap) uint { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *UIntKey) From(t Tag) uint { return uint(t.packed) } - -// UInt8Key represents a key -type UInt8Key struct { - name string - description string -} - -// NewUInt8Key creates a new Key for uint8 values. -func NewUInt8Key(name, description string) *UInt8Key { - return &UInt8Key{name: name, description: description} -} - -func (k *UInt8Key) Name() string { return k.name } -func (k *UInt8Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *UInt8Key) Of(v uint8) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *UInt8Key) Get(tags TagMap) uint8 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *UInt8Key) From(t Tag) uint8 { return uint8(t.packed) } - -// UInt16Key represents a key -type UInt16Key struct { - name string - description string -} - -// NewUInt16Key creates a new Key for uint16 values. -func NewUInt16Key(name, description string) *UInt16Key { - return &UInt16Key{name: name, description: description} -} - -func (k *UInt16Key) Name() string { return k.name } -func (k *UInt16Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *UInt16Key) Of(v uint16) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *UInt16Key) Get(tags TagMap) uint16 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *UInt16Key) From(t Tag) uint16 { return uint16(t.packed) } - -// UInt32Key represents a key -type UInt32Key struct { - name string - description string -} - -// NewUInt32Key creates a new Key for uint32 values. -func NewUInt32Key(name, description string) *UInt32Key { - return &UInt32Key{name: name, description: description} -} - -func (k *UInt32Key) Name() string { return k.name } -func (k *UInt32Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *UInt32Key) Of(v uint32) Tag { return Tag{Key: k, packed: uint64(v)} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *UInt32Key) Get(tags TagMap) uint32 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *UInt32Key) From(t Tag) uint32 { return uint32(t.packed) } - -// UInt64Key represents a key -type UInt64Key struct { - name string - description string -} - -// NewUInt64Key creates a new Key for uint64 values. -func NewUInt64Key(name, description string) *UInt64Key { - return &UInt64Key{name: name, description: description} -} - -func (k *UInt64Key) Name() string { return k.name } -func (k *UInt64Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *UInt64Key) Of(v uint64) Tag { return Tag{Key: k, packed: v} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *UInt64Key) Get(tags TagMap) uint64 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *UInt64Key) From(t Tag) uint64 { return t.packed } - -// Float32Key represents a key -type Float32Key struct { - name string - description string -} - -// NewFloat32Key creates a new Key for float32 values. -func NewFloat32Key(name, description string) *Float32Key { - return &Float32Key{name: name, description: description} -} - -func (k *Float32Key) Name() string { return k.name } -func (k *Float32Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *Float32Key) Of(v float32) Tag { - return Tag{Key: k, packed: uint64(math.Float32bits(v))} -} - -// Get can be used to get a tag for the key from a TagMap. -func (k *Float32Key) Get(tags TagMap) float32 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *Float32Key) From(t Tag) float32 { - return math.Float32frombits(uint32(t.packed)) -} - -// Float64Key represents a key -type Float64Key struct { - name string - description string -} - -// NewFloat64Key creates a new Key for int64 values. -func NewFloat64Key(name, description string) *Float64Key { - return &Float64Key{name: name, description: description} -} - -func (k *Float64Key) Name() string { return k.name } -func (k *Float64Key) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *Float64Key) Of(v float64) Tag { - return Tag{Key: k, packed: math.Float64bits(v)} -} - -// Get can be used to get a tag for the key from a TagMap. -func (k *Float64Key) Get(tags TagMap) float64 { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Tag. -func (k *Float64Key) From(t Tag) float64 { - return math.Float64frombits(t.packed) -} - -// StringKey represents a key -type StringKey struct { - name string - description string -} - -// NewStringKey creates a new Key for int64 values. -func NewStringKey(name, description string) *StringKey { - return &StringKey{name: name, description: description} -} - -func (k *StringKey) Name() string { return k.name } -func (k *StringKey) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *StringKey) Of(v string) Tag { return Tag{Key: k, str: v} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *StringKey) Get(tags TagMap) string { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return "" -} - -// From can be used to get a value from a Tag. -func (k *StringKey) From(t Tag) string { return t.str } - -// BooleanKey represents a key -type BooleanKey struct { - name string - description string -} - -// NewBooleanKey creates a new Key for bool values. -func NewBooleanKey(name, description string) *BooleanKey { - return &BooleanKey{name: name, description: description} -} - -func (k *BooleanKey) Name() string { return k.name } -func (k *BooleanKey) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *BooleanKey) Of(v bool) Tag { - t := Tag{Key: k} - if v { - t.packed = 1 - } - return t -} - -// Get can be used to get a tag for the key from a TagMap. -func (k *BooleanKey) Get(tags TagMap) bool { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return false -} - -// From can be used to get a value from a Tag. -func (k *BooleanKey) From(t Tag) bool { return t.packed > 0 } - -// ErrorKey represents a key -type ErrorKey struct { - name string - description string -} - -// NewErrorKey creates a new Key for int64 values. -func NewErrorKey(name, description string) *ErrorKey { - return &ErrorKey{name: name, description: description} -} - -func (k *ErrorKey) Name() string { return k.name } -func (k *ErrorKey) Description() string { return k.description } - -// Of creates a new Tag with this key and the supplied value. -func (k *ErrorKey) Of(v error) Tag { return Tag{Key: k, untyped: v} } - -// Get can be used to get a tag for the key from a TagMap. -func (k *ErrorKey) Get(tags TagMap) error { - if t := tags.Find(k); t.Valid() { - return k.From(t) - } - return nil -} - -// From can be used to get a value from a Tag. -func (k *ErrorKey) From(t Tag) error { - err, _ := t.untyped.(error) - return err -} diff --git a/internal/vendored/go-tools/telemetry/event/label.go b/internal/vendored/go-tools/telemetry/event/label.go deleted file mode 100644 index edd34bb0..00000000 --- a/internal/vendored/go-tools/telemetry/event/label.go +++ /dev/null @@ -1,29 +0,0 @@ -// 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" -) - -// Label sends a label event to the exporter with the supplied tags. -func Label(ctx context.Context, tags ...Tag) context.Context { - return dispatch(ctx, makeEvent(LabelType, sTags{}, tags)) -} - -// Label1 sends a label event to the exporter with the supplied tags. -func Label1(ctx context.Context, t1 Tag) context.Context { - return dispatch(ctx, makeEvent(LabelType, sTags{t1}, nil)) -} - -// Label2 sends a label event to the exporter with the supplied tags. -func Label2(ctx context.Context, t1, t2 Tag) context.Context { - return dispatch(ctx, makeEvent(LabelType, sTags{t1, t2}, nil)) -} - -// Label3 sends a label event to the exporter with the supplied tags. -func Label3(ctx context.Context, t1, t2, t3 Tag) context.Context { - return dispatch(ctx, makeEvent(LabelType, sTags{t1, t2, t3}, nil)) -} diff --git a/internal/vendored/go-tools/telemetry/event/log.go b/internal/vendored/go-tools/telemetry/event/log.go deleted file mode 100644 index 568aa1b9..00000000 --- a/internal/vendored/go-tools/telemetry/event/log.go +++ /dev/null @@ -1,59 +0,0 @@ -// 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" - "errors" -) - -// Log sends a log event with the supplied tag list to the exporter. -func Log(ctx context.Context, tags ...Tag) { - dispatch(ctx, makeEvent(LogType, sTags{}, tags)) -} - -// Log1 sends a label event to the exporter with the supplied tags. -func Log1(ctx context.Context, t1 Tag) context.Context { - return dispatch(ctx, makeEvent(LogType, sTags{t1}, nil)) -} - -// Log2 sends a label event to the exporter with the supplied tags. -func Log2(ctx context.Context, t1, t2 Tag) context.Context { - return dispatch(ctx, makeEvent(LogType, sTags{t1, t2}, nil)) -} - -// Log3 sends a label event to the exporter with the supplied tags. -func Log3(ctx context.Context, t1, t2, t3 Tag) context.Context { - return dispatch(ctx, makeEvent(LogType, sTags{t1, t2, t3}, nil)) -} - -// Print takes a message and a tag list and combines them into a single event -// before delivering them to the exporter. -func Print(ctx context.Context, message string, tags ...Tag) { - dispatch(ctx, makeEvent(LogType, sTags{Msg.Of(message)}, tags)) -} - -// Print1 takes a message and one tag delivers a log event to the exporter. -// It is a customized version of Print that is faster and does no allocation. -func Print1(ctx context.Context, message string, t1 Tag) { - dispatch(ctx, makeEvent(LogType, sTags{Msg.Of(message), t1}, nil)) -} - -// Print2 takes a message and two tags and delivers a log event to the exporter. -// It is a customized version of Print that is faster and does no allocation. -func Print2(ctx context.Context, message string, t1 Tag, t2 Tag) { - dispatch(ctx, makeEvent(LogType, sTags{Msg.Of(message), t1, t2}, nil)) -} - -// Error takes a message and a tag 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, tags ...Tag) { - if err == nil { - err = errors.New(message) - message = "" - } - dispatch(ctx, makeEvent(LogType, sTags{Msg.Of(message), Err.Of(err)}, tags)) -} diff --git a/internal/vendored/go-tools/telemetry/event/metric.go b/internal/vendored/go-tools/telemetry/event/metric.go deleted file mode 100644 index e4092bb0..00000000 --- a/internal/vendored/go-tools/telemetry/event/metric.go +++ /dev/null @@ -1,29 +0,0 @@ -// 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" -) - -// Record sends a label event to the exporter with the supplied tags. -func Record(ctx context.Context, tags ...Tag) context.Context { - return dispatch(ctx, makeEvent(RecordType, sTags{}, tags)) -} - -// Record1 sends a label event to the exporter with the supplied tags. -func Record1(ctx context.Context, t1 Tag) context.Context { - return dispatch(ctx, makeEvent(RecordType, sTags{t1}, nil)) -} - -// Record2 sends a label event to the exporter with the supplied tags. -func Record2(ctx context.Context, t1, t2 Tag) context.Context { - return dispatch(ctx, makeEvent(RecordType, sTags{t1, t2}, nil)) -} - -// Record3 sends a label event to the exporter with the supplied tags. -func Record3(ctx context.Context, t1, t2, t3 Tag) context.Context { - return dispatch(ctx, makeEvent(RecordType, sTags{t1, t2, t3}, nil)) -} diff --git a/internal/vendored/go-tools/telemetry/event/tag.go b/internal/vendored/go-tools/telemetry/event/tag.go deleted file mode 100644 index 7d7a34c0..00000000 --- a/internal/vendored/go-tools/telemetry/event/tag.go +++ /dev/null @@ -1,256 +0,0 @@ -// 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 ( - "fmt" -) - -// Tag holds a key and value pair. -// It is normally used when passing around lists of tags. -type Tag struct { - Key Key - packed uint64 - str string - untyped interface{} -} - -// TagMap is the interface to a collection of Tags indexed by key. -type TagMap interface { - // Find returns the tag that matches the supplied key. - Find(key interface{}) Tag -} - -// TagPointer is the interface to something that provides an iterable -// list of tags. -type TagPointer interface { - // Next advances to the next entry in the list and return a TagIterator for it. - // It will return nil if there are no more entries. - Next() TagPointer - // Tag returns the tag the pointer is for. - Tag() Tag -} - -// TagIterator is used to iterate through tags using TagPointer. -// It is a small helper that will normally fully inline to make it easier to -// manage the fact that pointer advance returns a new pointer rather than -// moving the existing one. -type TagIterator struct { - ptr TagPointer -} - -// tagPointer implements TagPointer over a simple list of tags. -type tagPointer struct { - tags []Tag -} - -// tagPointer wraps a TagPointer filtering out specific tags. -type tagFilter struct { - filter []Key - underlying TagPointer -} - -// tagPointerChain implements TagMap for a list of underlying TagMap. -type tagPointerChain struct { - ptrs []TagPointer -} - -// tagMap implements TagMap for a simple list of tags. -type tagMap struct { - tags []Tag -} - -// tagMapChain implements TagMap for a list of underlying TagMap. -type tagMapChain struct { - maps []TagMap -} - -// Valid returns true if the Tag is a valid one (it has a key). -func (t Tag) Valid() bool { return t.Key != nil } - -// Format is used for debug printing of tags. -func (t Tag) Format(f fmt.State, r rune) { - if !t.Valid() { - fmt.Fprintf(f, `nil`) - return - } - switch key := t.Key.(type) { - case *IntKey: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *Int8Key: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *Int16Key: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *Int32Key: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *Int64Key: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *UIntKey: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *UInt8Key: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *UInt16Key: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *UInt32Key: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *UInt64Key: - fmt.Fprintf(f, "%s=%d", key.Name(), key.From(t)) - case *Float32Key: - fmt.Fprintf(f, "%s=%g", key.Name(), key.From(t)) - case *Float64Key: - fmt.Fprintf(f, "%s=%g", key.Name(), key.From(t)) - case *BooleanKey: - fmt.Fprintf(f, "%s=%t", key.Name(), key.From(t)) - case *StringKey: - fmt.Fprintf(f, "%s=%q", key.Name(), key.From(t)) - case *ErrorKey: - fmt.Fprintf(f, "%s=%q", key.Name(), key.From(t)) - case *ValueKey: - fmt.Fprintf(f, "%s=%q", key.Name(), key.From(t)) - default: - fmt.Fprintf(f, `%s="invalid type %T"`, key.Name(), key) - } -} - -func (i *TagIterator) Valid() bool { - return i.ptr != nil -} - -func (i *TagIterator) Advance() { - i.ptr = i.ptr.Next() -} - -func (i *TagIterator) Tag() Tag { - return i.ptr.Tag() -} - -func (i tagPointer) Next() TagPointer { - // loop until we are on a valid tag - for { - // move on one tag - i.tags = i.tags[1:] - // check if we have exhausted the current list - if len(i.tags) == 0 { - // no more tags, so no more iterator - return nil - } - // if the tag is valid, we are done - if i.tags[0].Valid() { - return i - } - } -} - -func (i tagPointer) Tag() Tag { - return i.tags[0] -} - -func (i tagFilter) Next() TagPointer { - // loop until we are on a valid tag - for { - i.underlying = i.underlying.Next() - if i.underlying == nil { - return nil - } - if !i.filtered() { - return i - } - } -} - -func (i tagFilter) filtered() bool { - tag := i.underlying.Tag() - for _, f := range i.filter { - if tag.Key == f { - return true - } - } - return false -} - -func (i tagFilter) Tag() Tag { - return i.underlying.Tag() -} - -func (i tagPointerChain) Next() TagPointer { - i.ptrs[0] = i.ptrs[0].Next() - if i.ptrs[0] == nil { - i.ptrs = i.ptrs[1:] - } - if len(i.ptrs) == 0 { - return nil - } - return i -} - -func (i tagPointerChain) Tag() Tag { - return i.ptrs[0].Tag() -} - -func (l tagMap) Find(key interface{}) Tag { - for _, tag := range l.tags { - if tag.Key == key { - return tag - } - } - return Tag{} -} - -func (c tagMapChain) Find(key interface{}) Tag { - for _, src := range c.maps { - tag := src.Find(key) - if tag.Valid() { - return tag - } - } - return Tag{} -} - -func NewTagIterator(tags ...Tag) TagIterator { - if len(tags) == 0 { - return TagIterator{} - } - result := TagIterator{ptr: tagPointer{tags: tags}} - if !result.Tag().Valid() { - result.Advance() - } - return result -} - -func Filter(it TagIterator, keys ...Key) TagIterator { - if !it.Valid() || len(keys) == 0 { - return it - } - ptr := tagFilter{filter: keys, underlying: it.ptr} - result := TagIterator{ptr: ptr} - if ptr.filtered() { - result.Advance() - } - return result -} - -func ChainTagIterators(iterators ...TagIterator) TagIterator { - if len(iterators) == 0 { - return TagIterator{} - } - ptrs := make([]TagPointer, 0, len(iterators)) - for _, it := range iterators { - if it.Valid() { - ptrs = append(ptrs, it.ptr) - } - } - if len(ptrs) == 0 { - return TagIterator{} - } - return TagIterator{ptr: tagPointerChain{ptrs: ptrs}} -} - -func NewTagMap(tags ...Tag) TagMap { - return tagMap{tags: tags} -} - -func MergeTagMaps(srcs ...TagMap) TagMap { - return tagMapChain{maps: srcs} -} diff --git a/internal/vendored/go-tools/telemetry/event/tag_test.go b/internal/vendored/go-tools/telemetry/event/tag_test.go deleted file mode 100644 index c0ab3bef..00000000 --- a/internal/vendored/go-tools/telemetry/event/tag_test.go +++ /dev/null @@ -1,304 +0,0 @@ -// 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 ( - "bytes" - "fmt" - "testing" - - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" -) - -var ( - AKey = event.NewStringKey("A", "") - BKey = event.NewStringKey("B", "") - CKey = event.NewStringKey("C", "") - A = AKey.Of("a") - B = BKey.Of("b") - C = CKey.Of("c") - all = []event.Tag{A, B, C} -) - -func TestTagIterator(t *testing.T) { - for _, test := range []struct { - name string - tags []event.Tag - expect string - }{{ - name: "empty", - }, { - name: "single", - tags: []event.Tag{A}, - expect: `A="a"`, - }, { - name: "invalid", - tags: []event.Tag{{}}, - expect: ``, - }, { - name: "two", - tags: []event.Tag{A, B}, - expect: `A="a", B="b"`, - }, { - name: "three", - tags: []event.Tag{A, B, C}, - expect: `A="a", B="b", C="c"`, - }, { - name: "missing A", - tags: []event.Tag{{}, B, C}, - expect: `B="b", C="c"`, - }, { - name: "missing B", - tags: []event.Tag{A, {}, C}, - expect: `A="a", C="c"`, - }, { - name: "missing C", - tags: []event.Tag{A, B, {}}, - expect: `A="a", B="b"`, - }, { - name: "missing AB", - tags: []event.Tag{{}, {}, C}, - expect: `C="c"`, - }, { - name: "missing AC", - tags: []event.Tag{{}, B, {}}, - expect: `B="b"`, - }, { - name: "missing BC", - tags: []event.Tag{A, {}, {}}, - expect: `A="a"`, - }} { - t.Run(test.name, func(t *testing.T) { - got := printIterator(event.NewTagIterator(test.tags...)) - if got != test.expect { - t.Errorf("got %q want %q", got, test.expect) - } - }) - } -} - -func TestTagFilter(t *testing.T) { - for _, test := range []struct { - name string - tags []event.Tag - filters []event.Key - expect string - }{{ - name: "no filters", - tags: all, - expect: `A="a", B="b", C="c"`, - }, { - name: "no tags", - filters: []event.Key{AKey}, - expect: ``, - }, { - name: "filter A", - tags: all, - filters: []event.Key{AKey}, - expect: `B="b", C="c"`, - }, { - name: "filter B", - tags: all, - filters: []event.Key{BKey}, - expect: `A="a", C="c"`, - }, { - name: "filter C", - tags: all, - filters: []event.Key{CKey}, - expect: `A="a", B="b"`, - }, { - name: "filter AC", - tags: all, - filters: []event.Key{AKey, CKey}, - expect: `B="b"`, - }} { - t.Run(test.name, func(t *testing.T) { - tags := event.NewTagIterator(test.tags...) - got := printIterator(event.Filter(tags, test.filters...)) - if got != test.expect { - t.Errorf("got %q want %q", got, test.expect) - } - }) - } -} - -func TestTagChain(t *testing.T) { - for _, test := range []struct { - name string - tags [][]event.Tag - expect string - }{{ - name: "no iterators", - expect: ``, - }, { - name: "one iterator", - tags: [][]event.Tag{all}, - expect: `A="a", B="b", C="c"`, - }, { - name: "invalid iterator", - tags: [][]event.Tag{{}}, - expect: ``, - }, { - name: "two iterators", - tags: [][]event.Tag{{B, C}, {A}}, - expect: `B="b", C="c", A="a"`, - }, { - name: "invalid start iterator", - tags: [][]event.Tag{{}, {B, C}}, - expect: `B="b", C="c"`, - }, { - name: "invalid mid iterator", - tags: [][]event.Tag{{A}, {}, {C}}, - expect: `A="a", C="c"`, - }, { - name: "invalid end iterator", - tags: [][]event.Tag{{B, C}, {}}, - expect: `B="b", C="c"`, - }} { - t.Run(test.name, func(t *testing.T) { - iterators := make([]event.TagIterator, len(test.tags)) - for i, v := range test.tags { - iterators[i] = event.NewTagIterator(v...) - } - got := printIterator(event.ChainTagIterators(iterators...)) - if got != test.expect { - t.Errorf("got %q want %q", got, test.expect) - } - }) - } -} - -func TestTagMap(t *testing.T) { - for _, test := range []struct { - name string - tags []event.Tag - keys []event.Key - expect string - }{{ - name: "no tags", - keys: []event.Key{AKey}, - expect: `nil`, - }, { - name: "match A", - tags: all, - keys: []event.Key{AKey}, - expect: `A="a"`, - }, { - name: "match B", - tags: all, - keys: []event.Key{BKey}, - expect: `B="b"`, - }, { - name: "match C", - tags: all, - keys: []event.Key{CKey}, - expect: `C="c"`, - }, { - name: "match ABC", - tags: all, - keys: []event.Key{AKey, BKey, CKey}, - expect: `A="a", B="b", C="c"`, - }, { - name: "missing A", - tags: []event.Tag{{}, B, C}, - keys: []event.Key{AKey, BKey, CKey}, - expect: `nil, B="b", C="c"`, - }, { - name: "missing B", - tags: []event.Tag{A, {}, C}, - keys: []event.Key{AKey, BKey, CKey}, - expect: `A="a", nil, C="c"`, - }, { - name: "missing C", - tags: []event.Tag{A, B, {}}, - keys: []event.Key{AKey, BKey, CKey}, - expect: `A="a", B="b", nil`, - }} { - t.Run(test.name, func(t *testing.T) { - tagMap := event.NewTagMap(test.tags...) - got := printTagMap(tagMap, test.keys) - if got != test.expect { - t.Errorf("got %q want %q", got, test.expect) - } - }) - } -} - -func TestTagMapMerge(t *testing.T) { - for _, test := range []struct { - name string - tags [][]event.Tag - keys []event.Key - expect string - }{{ - name: "no maps", - keys: []event.Key{AKey}, - expect: `nil`, - }, { - name: "one map", - tags: [][]event.Tag{all}, - keys: []event.Key{AKey}, - expect: `A="a"`, - }, { - name: "invalid map", - tags: [][]event.Tag{{}}, - keys: []event.Key{AKey}, - expect: `nil`, - }, { - name: "two maps", - tags: [][]event.Tag{{B, C}, {A}}, - keys: []event.Key{AKey, BKey, CKey}, - expect: `A="a", B="b", C="c"`, - }, { - name: "invalid start map", - tags: [][]event.Tag{{}, {B, C}}, - keys: []event.Key{AKey, BKey, CKey}, - expect: `nil, B="b", C="c"`, - }, { - name: "invalid mid map", - tags: [][]event.Tag{{A}, {}, {C}}, - keys: []event.Key{AKey, BKey, CKey}, - expect: `A="a", nil, C="c"`, - }, { - name: "invalid end map", - tags: [][]event.Tag{{A, B}, {}}, - keys: []event.Key{AKey, BKey, CKey}, - expect: `A="a", B="b", nil`, - }} { - t.Run(test.name, func(t *testing.T) { - maps := make([]event.TagMap, len(test.tags)) - for i, v := range test.tags { - maps[i] = event.NewTagMap(v...) - } - tagMap := event.MergeTagMaps(maps...) - got := printTagMap(tagMap, test.keys) - if got != test.expect { - t.Errorf("got %q want %q", got, test.expect) - } - }) - } -} - -func printIterator(it event.TagIterator) string { - buf := &bytes.Buffer{} - for ; it.Valid(); it.Advance() { - if buf.Len() > 0 { - buf.WriteString(", ") - } - fmt.Fprint(buf, it.Tag()) - } - return buf.String() -} - -func printTagMap(tagMap event.TagMap, keys []event.Key) string { - buf := &bytes.Buffer{} - for _, key := range keys { - if buf.Len() > 0 { - buf.WriteString(", ") - } - fmt.Fprint(buf, tagMap.Find(key)) - } - return buf.String() -} diff --git a/internal/vendored/go-tools/telemetry/event/trace.go b/internal/vendored/go-tools/telemetry/event/trace.go deleted file mode 100644 index 2c40341f..00000000 --- a/internal/vendored/go-tools/telemetry/event/trace.go +++ /dev/null @@ -1,42 +0,0 @@ -// 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" -) - -// StartSpan sends a span start event with the supplied tag list to the exporter. -// It also returns a function that will end the span, which should normally be -// deferred. -func StartSpan(ctx context.Context, name string, tags ...Tag) (context.Context, func()) { - return dispatchPair(ctx, - makeEvent(StartSpanType, sTags{Name.Of(name)}, tags), - makeEvent(EndSpanType, sTags{}, nil)) -} - -// StartSpan1 sends a span start event with the supplied tag list to the exporter. -// It also returns a function that will end the span, which should normally be -// deferred. -func StartSpan1(ctx context.Context, name string, t1 Tag) (context.Context, func()) { - return dispatchPair(ctx, - makeEvent(StartSpanType, sTags{Name.Of(name), t1}, nil), - makeEvent(EndSpanType, sTags{}, nil)) -} - -// StartSpan2 sends a span start event with the supplied tag list to the exporter. -// It also returns a function that will end the span, which should normally be -// deferred. -func StartSpan2(ctx context.Context, name string, t1, t2 Tag) (context.Context, func()) { - return dispatchPair(ctx, - makeEvent(StartSpanType, sTags{Name.Of(name), t1, t2}, nil), - makeEvent(EndSpanType, sTags{}, nil)) -} - -// 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 dispatch(ctx, makeEvent(DetachType, sTags{}, nil)) -} diff --git a/internal/vendored/go-tools/telemetry/export/metric/exporter.go b/internal/vendored/go-tools/telemetry/export/metric/exporter.go deleted file mode 100644 index e7acbe8a..00000000 --- a/internal/vendored/go-tools/telemetry/export/metric/exporter.go +++ /dev/null @@ -1,52 +0,0 @@ -// 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" - - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" -) - -var Entries = event.NewKey("metric_entries", "The set of metrics calculated for an event") - -type Config struct { - subscribers map[interface{}][]subscriber -} - -type subscriber func(time.Time, event.TagMap, event.Tag) Data - -func (e *Config) subscribe(key event.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 event.Event, tagMap event.TagMap) context.Context { - if !ev.IsRecord() { - return output(ctx, ev, tagMap) - } - mu.Lock() - defer mu.Unlock() - var metrics []Data - for it := ev.Tags(); it.Valid(); it.Advance() { - tag := it.Tag() - id := tag.Key - if list := e.subscribers[id]; len(list) > 0 { - for _, s := range list { - metrics = append(metrics, s(ev.At, tagMap, tag)) - } - } - } - tagMap = event.MergeTagMaps(event.NewTagMap(Entries.Of(metrics)), tagMap) - return output(ctx, ev, tagMap) - } -} diff --git a/internal/vendored/go-tools/telemetry/export/tag.go b/internal/vendored/go-tools/telemetry/export/tag.go deleted file mode 100644 index a5899498..00000000 --- a/internal/vendored/go-tools/telemetry/export/tag.go +++ /dev/null @@ -1,35 +0,0 @@ -// 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" - - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/telemetry/event" -) - -// Labels builds an exporter that manipulates the context using the event. -// If the event is type IsTag or IsStartSpan then it returns a context updated -// with tag values from the event. -// For all other event types the event tags 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 event.Event, tagMap event.TagMap) context.Context { - stored, _ := ctx.Value(labelContextKey).(event.TagMap) - if ev.IsLabel() || ev.IsStartSpan() { - // update the tag source stored in the context - fromEvent := ev.Map() - if stored == nil { - stored = fromEvent - } else { - stored = event.MergeTagMaps(fromEvent, stored) - } - ctx = context.WithValue(ctx, labelContextKey, stored) - } - // add the stored tag context to the tag source - tagMap = event.MergeTagMaps(tagMap, stored) - return output(ctx, ev, tagMap) - } -} diff --git a/internal/vendored/go-tools/telemetry/unit/unit.go b/internal/vendored/go-tools/telemetry/unit/unit.go deleted file mode 100644 index 596a5c77..00000000 --- a/internal/vendored/go-tools/telemetry/unit/unit.go +++ /dev/null @@ -1,19 +0,0 @@ -// 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 unit holds the definitions for the units you can use in telemetry. -package unit - -// Unit is used to specify the units for a given measure. -// This is can used for display purposes. -type Unit string - -const ( - // Dimensionless indicates that a measure has no specified units. - Dimensionless = "1" - // Bytes indicates that that a measure is recording number of bytes. - Bytes = "By" - // Milliseconds indicates that a measure is recording a duration in milliseconds. - Milliseconds = "ms" -) diff --git a/langserver/cache/cache.go b/langserver/cache/cache.go index c0f5b38e..4aab49ce 100644 --- a/langserver/cache/cache.go +++ b/langserver/cache/cache.go @@ -16,6 +16,7 @@ package cache import ( "context" "errors" + "fmt" "go/token" "sync" @@ -56,7 +57,7 @@ func (c *DocumentCache) AddDocument(serverLifetime context.Context, doc *protoco if r := recover(); r != nil { if err, ok := r.(error); !ok { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "cache/addDocument: %v", err) + return nil, fmt.Errorf("%w: cache/addDocument: %v", jsonrpc2.ErrInternal, err) } } @@ -90,7 +91,7 @@ func (c *DocumentCache) GetDocument(uri protocol.DocumentURI) (*DocumentHandle, ret, ok := c.documents[uri] if !ok { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "cache/getDocument: Document not found: %v", uri) + return nil, fmt.Errorf("%w: cache/getDocument: Document not found: %v", jsonrpc2.ErrInternal, uri) } ret.mu.RLock() diff --git a/langserver/cache/document.go b/langserver/cache/document.go index 5742a7d5..55f15429 100644 --- a/langserver/cache/document.go +++ b/langserver/cache/document.go @@ -17,6 +17,7 @@ import ( "bytes" "context" "errors" + "fmt" "go/token" "sync" @@ -70,7 +71,7 @@ func (d *DocumentHandle) ApplyIncrementalChanges(changes []protocol.TextDocument defer d.doc.mu.RUnlock() if version <= d.doc.version { - return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Update to file didn't increase version number") + return "", fmt.Errorf("%w, Update to file didn't increase version number", jsonrpc2.ErrInvalidParams) } content := []byte(d.doc.content) @@ -92,12 +93,12 @@ func (d *DocumentHandle) ApplyIncrementalChanges(changes []protocol.TextDocument } if !spn.HasOffset() { - return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change") + return "", fmt.Errorf("%w: invalid range for content change", jsonrpc2.ErrInvalidParams) } start, end := spn.Start().Offset(), spn.End().Offset() if end < start { - return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change") + return "", fmt.Errorf("%w: invalid range for content change", jsonrpc2.ErrInvalidParams) } var buf bytes.Buffer @@ -120,11 +121,11 @@ func (d *DocumentHandle) SetContent(serverLifetime context.Context, content stri defer d.doc.mu.Unlock() if !new && version <= d.doc.version { - return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Update to file didn't increase version number") + return fmt.Errorf("%w: Update to file didn't increase version number", jsonrpc2.ErrInvalidParams) } if len(content) > maxDocumentSize { - return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "cache/SetContent: Provided.document to large.") + return fmt.Errorf("%w: cache/SetContent: Provided.document to large.", jsonrpc2.ErrInvalidParams) } if !new { diff --git a/langserver/general.go b/langserver/general.go index 5a924cf9..4ada7017 100644 --- a/langserver/general.go +++ b/langserver/general.go @@ -16,6 +16,7 @@ package langserver import ( "context" "errors" + "fmt" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/lsp/protocol" @@ -28,7 +29,7 @@ func (s *server) Initialize(_ context.Context, _ *protocol.ParamInitialize) (*pr defer s.stateMu.Unlock() if s.state != serverCreated { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server already initialized") + return nil, fmt.Errorf("%w: server already initialized", jsonrpc2.ErrInvalidRequest) } s.state = serverInitializing @@ -93,7 +94,7 @@ func (s *server) Shutdown(_ context.Context) error { defer s.stateMu.Unlock() if s.state != serverInitialized { - return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized") + return fmt.Errorf("%w: server not initialized", jsonrpc2.ErrInvalidRequest) } s.state = serverShutDown @@ -108,7 +109,7 @@ func (s *server) Exit(_ context.Context) error { defer s.stateMu.Unlock() if s.state != serverShutDown { - return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not shutdown") + return fmt.Errorf("%w: server not shutdown", jsonrpc2.ErrInvalidRequest) } s.exit() diff --git a/langserver/jsonloggingstream.go b/langserver/jsonloggingstream.go index f79dd648..107580b4 100644 --- a/langserver/jsonloggingstream.go +++ b/langserver/jsonloggingstream.go @@ -15,14 +15,14 @@ package langserver import ( "context" - "encoding/json" "fmt" "io" "os" "time" + "encoding/json" + "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/jsonrpc2" - "github.com/prometheus-community/promql-langserver/internal/vendored/go-tools/lsp/protocol" ) type jsonLogStream struct { @@ -38,58 +38,61 @@ func jSONLogStream(str jsonrpc2.Stream, w io.Writer) jsonrpc2.Stream { return ret } -func (s *jsonLogStream) Read(ctx context.Context) ([]byte, int64, error) { +func (s *jsonLogStream) Read(ctx context.Context) (jsonrpc2.Message, int64, error) { data, count, err := s.mainStream.Read(ctx) s.log(data, true) return data, count, err } -func (s *jsonLogStream) Write(ctx context.Context, data []byte) (int64, error) { - count, err := s.mainStream.Write(ctx, data) - s.log(data, false) +func (s *jsonLogStream) Write(ctx context.Context, msg jsonrpc2.Message) (int64, error) { + count, err := s.mainStream.Write(ctx, msg) + s.log(msg, false) return count, err } -func getType(msg []byte, incoming bool) (string, error) { - var v protocol.Combined +func (s *jsonLogStream) Close() error { + return s.mainStream.Close() +} +func getType(msg jsonrpc2.Message, incoming bool) (string, error) { var msgType string - err := json.Unmarshal(msg, &v) - if err != nil { - return "", err - } - if incoming { msgType = "send-" } else { msgType = "receive-" } - switch { - case v.ID != nil && v.Method != "" && (v.Params != nil || v.Method == "shutdown"): + switch msg.(type) { + case *jsonrpc2.Call: msgType += "request" - case v.ID != nil && v.Method == "" && v.Params == nil: + case *jsonrpc2.Response: msgType += "response" - default: + case *jsonrpc2.Notification: msgType += "notification" } return msgType, nil } -func (s *jsonLogStream) log(msg []byte, incoming bool) { +func (s *jsonLogStream) log(msg jsonrpc2.Message, incoming bool) { typ, err := getType(msg, incoming) if err != nil { fmt.Fprintln(os.Stderr, err) return } + msgText, err := json.Marshal(msg) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + timestamp := time.Now().UnixNano() / 1000000 tmformat := time.Now().Format("03:04:15.000 PM") // The LSP inspector expects the [LSP -