internal/lsp/debug: add a facility to track known bugs

Sometimes users report issues related to edge cases in Gopls that aren't
reproducible. In some of these cases, we end up guarding against
conditions that shouldn't be possible, which is an unfortunately fragile
solution.

Add a new debug.Bug function to both annotate such branches as known
bugs, and help find them when they reoccur.  For now this just records
them in the debug server, but in the future we could send the user a
message to the effect of "hey, a known bug has occurred" for debug
builds of gopls.

Also included are some minor cosmetic fixes.

Change-Id: I95df0caf2c81f430661cabd573ce8e338fa69934
Reviewed-on: https://go-review.googlesource.com/c/tools/+/318369
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Rob Findley 2021-05-10 10:27:35 -04:00 коммит произвёл Robert Findley
Родитель 5a667787ee
Коммит fa05545715
4 изменённых файлов: 54 добавлений и 3 удалений

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright 2021 The Go Authors. All rights reserved. // Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package misc package misc
import ( import (

Просмотреть файл

@ -27,13 +27,13 @@ type Exporter struct {
metrics []metric.Data metrics []metric.Data
} }
func (e *Exporter) ProcessEvent(ctx context.Context, ev core.Event, ln label.Map) context.Context { func (e *Exporter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
if !event.IsMetric(ev) { if !event.IsMetric(ev) {
return ctx return ctx
} }
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock() defer e.mu.Unlock()
metrics := metric.Entries.Get(ln).([]metric.Data) metrics := metric.Entries.Get(lm).([]metric.Data)
for _, data := range metrics { for _, data := range metrics {
name := data.Handle() name := data.Handle()
// We keep the metrics in name sorted order so the page is stable and easy // We keep the metrics in name sorted order so the page is stable and easy

Просмотреть файл

@ -20,6 +20,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
rpprof "runtime/pprof" rpprof "runtime/pprof"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -76,6 +77,46 @@ type State struct {
mu sync.Mutex mu sync.Mutex
clients []*Client clients []*Client
servers []*Server servers []*Server
// bugs maps bug description -> formatted event
bugs map[string]string
}
func Bug(ctx context.Context, desc string) {
labels := [3]label.Label{
tag.Bug.Of(desc),
}
_, file, line, ok := runtime.Caller(1)
if ok {
labels[1] = tag.Callsite.Of(fmt.Sprintf("%s:%d", file, line))
}
core.Export(ctx, core.MakeEvent(labels, nil))
}
type bug struct {
Description, Event string
}
func (st *State) Bugs() []bug {
st.mu.Lock()
defer st.mu.Unlock()
var bugs []bug
for k, v := range st.bugs {
bugs = append(bugs, bug{k, v})
}
sort.Slice(bugs, func(i, j int) bool {
return bugs[i].Description < bugs[j].Description
})
return bugs
}
func (st *State) recordBug(description, event string) {
st.mu.Lock()
defer st.mu.Unlock()
if st.bugs == nil {
st.bugs = make(map[string]string)
}
st.bugs[description] = event
} }
// Caches returns the set of Cache objects currently being served. // Caches returns the set of Cache objects currently being served.
@ -338,7 +379,7 @@ func (i *Instance) AddService(s protocol.Server, session *cache.Session) {
stdlog.Printf("unable to find a Client to add the protocol.Server to") stdlog.Printf("unable to find a Client to add the protocol.Server to")
} }
func getMemory(r *http.Request) interface{} { func getMemory(_ *http.Request) interface{} {
var m runtime.MemStats var m runtime.MemStats
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)
return m return m
@ -636,6 +677,9 @@ func makeInstanceExporter(i *Instance) event.Exporter {
} }
} }
} }
if b := tag.Bug.Get(ev); b != "" {
i.State.recordBug(b, fmt.Sprintf("%v", ev))
}
return ctx return ctx
} }
// StdTrace must be above export.Spans below (by convention, export // StdTrace must be above export.Spans below (by convention, export
@ -766,6 +810,8 @@ var MainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
<ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul> <ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul>
<h2>Servers</h2> <h2>Servers</h2>
<ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul> <ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
<h2>Known bugs encountered</h2>
<dl>{{range .State.Bugs}}<dt>{{.Description}}</dt><dd>{{.Event}}</dd>{{end}}</dl>
{{end}} {{end}}
`)) `))

Просмотреть файл

@ -43,6 +43,10 @@ var (
ClientID = keys.NewString("client_id", "") ClientID = keys.NewString("client_id", "")
Level = keys.NewInt("level", "The logging level") Level = keys.NewInt("level", "The logging level")
// Bug tracks occurrences of known bugs in the server.
Bug = keys.NewString("bug", "A bug has occurred")
Callsite = keys.NewString("callsite", "gopls function call site")
) )
var ( var (