diff --git a/internal/lsp/debug/prometheus.go b/internal/lsp/debug/prometheus.go new file mode 100644 index 00000000000..b8158bf2624 --- /dev/null +++ b/internal/lsp/debug/prometheus.go @@ -0,0 +1,111 @@ +// 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 debug + +import ( + "bytes" + "fmt" + "net/http" + "sort" + + "golang.org/x/tools/internal/lsp/telemetry/metric" + "golang.org/x/tools/internal/lsp/telemetry/tag" + "golang.org/x/tools/internal/lsp/telemetry/worker" +) + +type prometheus struct { + metrics []metric.Data +} + +func (p *prometheus) observeMetric(data metric.Data) { + name := data.Handle().Name() + index := sort.Search(len(p.metrics), func(i int) bool { + return p.metrics[i].Handle().Name() >= name + }) + if index >= len(p.metrics) || p.metrics[index].Handle().Name() != name { + old := p.metrics + p.metrics = make([]metric.Data, len(old)+1) + copy(p.metrics, old[:index]) + copy(p.metrics[index+1:], old[index:]) + } + p.metrics[index] = data +} + +func (p *prometheus) header(w http.ResponseWriter, name, description string, isGauge, isHistogram bool) { + kind := "counter" + if isGauge { + kind = "gauge" + } + if isHistogram { + kind = "histogram" + } + fmt.Fprintf(w, "# HELP %s %s\n", name, description) + fmt.Fprintf(w, "# TYPE %s %s\n", name, kind) +} + +func (p *prometheus) row(w http.ResponseWriter, name string, group tag.List, extra string, value interface{}) { + fmt.Fprint(w, name) + buf := &bytes.Buffer{} + fmt.Fprint(buf, group) + if extra != "" { + if buf.Len() > 0 { + fmt.Fprint(buf, ",") + } + fmt.Fprint(buf, extra) + } + if buf.Len() > 0 { + fmt.Fprint(w, "{") + buf.WriteTo(w) + fmt.Fprint(w, "}") + } + fmt.Fprintf(w, " %v\n", value) +} + +func (p *prometheus) serve(w http.ResponseWriter, r *http.Request) { + done := make(chan struct{}) + worker.Do(func() { + defer close(done) + for _, data := range p.metrics { + switch data := data.(type) { + case *metric.Int64Data: + p.header(w, data.Info.Name, data.Info.Description, data.IsGauge, false) + for i, group := range data.Groups() { + p.row(w, data.Info.Name, group, "", data.Rows[i]) + } + + case *metric.Float64Data: + p.header(w, data.Info.Name, data.Info.Description, data.IsGauge, false) + for i, group := range data.Groups() { + p.row(w, data.Info.Name, group, "", data.Rows[i]) + } + + case *metric.HistogramInt64Data: + p.header(w, data.Info.Name, data.Info.Description, false, true) + for i, group := range data.Groups() { + row := data.Rows[i] + for j, b := range data.Info.Buckets { + p.row(w, data.Info.Name+"_bucket", group, fmt.Sprintf(`le="%v"`, b), row.Values[j]) + } + p.row(w, data.Info.Name+"_bucket", group, `le="+Inf"`, row.Count) + p.row(w, data.Info.Name+"_count", group, "", row.Count) + p.row(w, data.Info.Name+"_sum", group, "", row.Sum) + } + + case *metric.HistogramFloat64Data: + p.header(w, data.Info.Name, data.Info.Description, false, true) + for i, group := range data.Groups() { + row := data.Rows[i] + for j, b := range data.Info.Buckets { + p.row(w, data.Info.Name+"_bucket", group, fmt.Sprintf(`le="%v"`, b), row.Values[j]) + } + p.row(w, data.Info.Name+"_bucket", group, `le="+Inf"`, row.Count) + p.row(w, data.Info.Name+"_count", group, "", row.Count) + p.row(w, data.Info.Name+"_sum", group, "", row.Sum) + } + } + } + }) + <-done +} diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go index c7c6a260fc1..26d8b559578 100644 --- a/internal/lsp/debug/serve.go +++ b/internal/lsp/debug/serve.go @@ -19,6 +19,7 @@ import ( "strconv" "sync" + "golang.org/x/tools/internal/lsp/telemetry/metric" "golang.org/x/tools/internal/span" ) @@ -212,6 +213,8 @@ func Serve(ctx context.Context, addr string) error { return err } log.Printf("Debug serving on port: %d", listener.Addr().(*net.TCPAddr).Port) + prometheus := prometheus{} + metric.RegisterObservers(prometheus.observeMetric) go func() { mux := http.NewServeMux() mux.HandleFunc("/", Render(mainTmpl, func(*http.Request) interface{} { return data })) @@ -221,6 +224,7 @@ func Serve(ctx context.Context, addr string) error { mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + mux.HandleFunc("/metrics/", prometheus.serve) mux.HandleFunc("/cache/", Render(cacheTmpl, getCache)) mux.HandleFunc("/session/", Render(sessionTmpl, getSession)) mux.HandleFunc("/view/", Render(viewTmpl, getView)) @@ -283,7 +287,7 @@ td.value { Main Info Memory -Debug +Metrics