1 // Copyright 2019 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
22 rpprof "runtime/pprof"
28 "golang.org/x/tools/internal/event"
29 "golang.org/x/tools/internal/event/core"
30 "golang.org/x/tools/internal/event/export"
31 "golang.org/x/tools/internal/event/export/metric"
32 "golang.org/x/tools/internal/event/export/ocagent"
33 "golang.org/x/tools/internal/event/export/prometheus"
34 "golang.org/x/tools/internal/event/keys"
35 "golang.org/x/tools/internal/event/label"
36 "golang.org/x/tools/internal/lsp/cache"
37 "golang.org/x/tools/internal/lsp/debug/log"
38 "golang.org/x/tools/internal/lsp/debug/tag"
39 "golang.org/x/tools/internal/lsp/protocol"
40 "golang.org/x/tools/internal/lsp/source"
41 errors "golang.org/x/xerrors"
44 type contextKeyType int
47 instanceKey contextKeyType = iota
51 // An Instance holds all debug information associated with a gopls instance.
52 type Instance struct {
57 ListenedDebugAddress string
63 exporter event.Exporter
65 ocagent *ocagent.Exporter
66 prometheus *prometheus.Exporter
72 // State holds debugging information related to the server state.
79 // Caches returns the set of Cache objects currently being served.
80 func (st *State) Caches() []*cache.Cache {
81 var caches []*cache.Cache
82 seen := make(map[string]struct{})
83 for _, client := range st.Clients() {
84 cache, ok := client.Session.Cache().(*cache.Cache)
88 if _, found := seen[cache.ID()]; found {
91 seen[cache.ID()] = struct{}{}
92 caches = append(caches, cache)
97 // Cache returns the Cache that matches the supplied id.
98 func (st *State) Cache(id string) *cache.Cache {
99 for _, c := range st.Caches() {
107 // Sessions returns the set of Session objects currently being served.
108 func (st *State) Sessions() []*cache.Session {
109 var sessions []*cache.Session
110 for _, client := range st.Clients() {
111 sessions = append(sessions, client.Session)
116 // Session returns the Session that matches the supplied id.
117 func (st *State) Session(id string) *cache.Session {
118 for _, s := range st.Sessions() {
126 // Views returns the set of View objects currently being served.
127 func (st *State) Views() []*cache.View {
128 var views []*cache.View
129 for _, s := range st.Sessions() {
130 for _, v := range s.Views() {
131 if cv, ok := v.(*cache.View); ok {
132 views = append(views, cv)
139 // View returns the View that matches the supplied id.
140 func (st *State) View(id string) *cache.View {
141 for _, v := range st.Views() {
149 // Clients returns the set of Clients currently being served.
150 func (st *State) Clients() []*Client {
153 clients := make([]*Client, len(st.clients))
154 copy(clients, st.clients)
158 // Client returns the Client matching the supplied id.
159 func (st *State) Client(id string) *Client {
160 for _, c := range st.Clients() {
161 if c.Session.ID() == id {
168 // Servers returns the set of Servers the instance is currently connected to.
169 func (st *State) Servers() []*Server {
172 servers := make([]*Server, len(st.servers))
173 copy(servers, st.servers)
177 // A Client is an incoming connection from a remote client.
179 Session *cache.Session
184 Service protocol.Server
187 // A Server is an outgoing connection to a remote LSP server.
196 // AddClient adds a client to the set being served.
197 func (st *State) addClient(session *cache.Session) {
200 st.clients = append(st.clients, &Client{Session: session})
203 // DropClient removes a client from the set being served.
204 func (st *State) dropClient(session source.Session) {
207 for i, c := range st.clients {
208 if c.Session == session {
209 copy(st.clients[i:], st.clients[i+1:])
210 st.clients[len(st.clients)-1] = nil
211 st.clients = st.clients[:len(st.clients)-1]
217 // AddServer adds a server to the set being queried. In practice, there should
218 // be at most one remote server.
219 func (st *State) addServer(server *Server) {
222 st.servers = append(st.servers, server)
225 // DropServer drops a server from the set being queried.
226 func (st *State) dropServer(id string) {
229 for i, s := range st.servers {
231 copy(st.servers[i:], st.servers[i+1:])
232 st.servers[len(st.servers)-1] = nil
233 st.servers = st.servers[:len(st.servers)-1]
239 // an http.ResponseWriter that filters writes
240 type filterResponse struct {
241 w http.ResponseWriter
242 edit func([]byte) []byte
245 func (c filterResponse) Header() http.Header {
249 func (c filterResponse) Write(buf []byte) (int, error) {
251 return c.w.Write(ans)
254 func (c filterResponse) WriteHeader(n int) {
258 // replace annoying nuls by spaces
259 func cmdline(w http.ResponseWriter, r *http.Request) {
260 fake := filterResponse{
262 edit: func(buf []byte) []byte {
263 return bytes.ReplaceAll(buf, []byte{0}, []byte{' '})
266 pprof.Cmdline(fake, r)
269 func (i *Instance) getCache(r *http.Request) interface{} {
270 return i.State.Cache(path.Base(r.URL.Path))
273 func (i *Instance) getSession(r *http.Request) interface{} {
274 return i.State.Session(path.Base(r.URL.Path))
277 func (i Instance) getClient(r *http.Request) interface{} {
278 return i.State.Client(path.Base(r.URL.Path))
281 func (i Instance) getServer(r *http.Request) interface{} {
283 defer i.State.mu.Unlock()
284 id := path.Base(r.URL.Path)
285 for _, s := range i.State.servers {
293 func (i Instance) getView(r *http.Request) interface{} {
294 return i.State.View(path.Base(r.URL.Path))
297 func (i *Instance) getFile(r *http.Request) interface{} {
298 identifier := path.Base(r.URL.Path)
299 sid := path.Base(path.Dir(r.URL.Path))
300 s := i.State.Session(sid)
304 for _, o := range s.Overlays() {
305 if o.FileIdentity().Hash == identifier {
312 func (i *Instance) getInfo(r *http.Request) interface{} {
313 buf := &bytes.Buffer{}
314 i.PrintServerInfo(r.Context(), buf)
315 return template.HTML(buf.String())
318 func (i *Instance) AddService(s protocol.Server, session *cache.Session) {
319 for _, c := range i.State.clients {
320 if c.Session == session {
325 stdlog.Printf("unable to find a Client to add the protocol.Server to")
328 func getMemory(r *http.Request) interface{} {
329 var m runtime.MemStats
330 runtime.ReadMemStats(&m)
335 event.SetExporter(makeGlobalExporter(os.Stderr))
338 func GetInstance(ctx context.Context) *Instance {
342 v := ctx.Value(instanceKey)
349 // WithInstance creates debug instance ready for use using the supplied
350 // configuration and stores it in the returned context.
351 func WithInstance(ctx context.Context, workdir, agent string) context.Context {
353 StartTime: time.Now(),
355 OCAgentConfig: agent,
357 i.LogWriter = os.Stderr
358 ocConfig := ocagent.Discover()
359 //TODO: we should not need to adjust the discovered configuration
360 ocConfig.Address = i.OCAgentConfig
361 i.ocagent = ocagent.Connect(ocConfig)
362 i.prometheus = prometheus.New()
366 i.exporter = makeInstanceExporter(i)
367 return context.WithValue(ctx, instanceKey, i)
370 // SetLogFile sets the logfile for use with this instance.
371 func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) {
372 // TODO: probably a better solution for deferring closure to the caller would
373 // be for the debug instance to itself be closed, but this fixes the
374 // immediate bug of logs not being captured.
375 closeLog := func() {}
377 if logfile == "auto" {
379 logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-daemon-%d.log", os.Getpid()))
381 logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
384 f, err := os.Create(logfile)
386 return nil, errors.Errorf("unable to create log file: %w", err)
391 stdlog.SetOutput(io.MultiWriter(os.Stderr, f))
398 // Serve starts and runs a debug server in the background.
399 // It also logs the port the server starts on, to allow for :0 auto assigned
401 func (i *Instance) Serve(ctx context.Context) error {
402 stdlog.SetFlags(stdlog.Lshortfile)
403 if i.DebugAddress == "" {
406 listener, err := net.Listen("tcp", i.DebugAddress)
410 i.ListenedDebugAddress = listener.Addr().String()
412 port := listener.Addr().(*net.TCPAddr).Port
413 if strings.HasSuffix(i.DebugAddress, ":0") {
414 stdlog.Printf("debug server listening at http://localhost:%d", port)
416 event.Log(ctx, "Debug serving", tag.Port.Of(port))
418 mux := http.NewServeMux()
419 mux.HandleFunc("/", render(MainTmpl, func(*http.Request) interface{} { return i }))
420 mux.HandleFunc("/debug/", render(DebugTmpl, nil))
421 mux.HandleFunc("/debug/pprof/", pprof.Index)
422 mux.HandleFunc("/debug/pprof/cmdline", cmdline)
423 mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
424 mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
425 mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
426 if i.prometheus != nil {
427 mux.HandleFunc("/metrics/", i.prometheus.Serve)
430 mux.HandleFunc("/rpc/", render(RPCTmpl, i.rpcs.getData))
433 mux.HandleFunc("/trace/", render(TraceTmpl, i.traces.getData))
435 mux.HandleFunc("/cache/", render(CacheTmpl, i.getCache))
436 mux.HandleFunc("/session/", render(SessionTmpl, i.getSession))
437 mux.HandleFunc("/view/", render(ViewTmpl, i.getView))
438 mux.HandleFunc("/client/", render(ClientTmpl, i.getClient))
439 mux.HandleFunc("/server/", render(ServerTmpl, i.getServer))
440 mux.HandleFunc("/file/", render(FileTmpl, i.getFile))
441 mux.HandleFunc("/info", render(InfoTmpl, i.getInfo))
442 mux.HandleFunc("/memory", render(MemoryTmpl, getMemory))
443 if err := http.Serve(listener, mux); err != nil {
444 event.Error(ctx, "Debug server failed", err)
447 event.Log(ctx, "Debug server finished")
452 // MonitorMemory starts recording memory statistics each second.
453 func (i *Instance) MonitorMemory(ctx context.Context) {
454 tick := time.NewTicker(time.Second)
455 nextThresholdGiB := uint64(1)
459 var mem runtime.MemStats
460 runtime.ReadMemStats(&mem)
461 if mem.HeapAlloc < nextThresholdGiB*1<<30 {
464 if err := i.writeMemoryDebug(nextThresholdGiB, true); err != nil {
465 event.Error(ctx, "writing memory debug info", err)
467 if err := i.writeMemoryDebug(nextThresholdGiB, false); err != nil {
468 event.Error(ctx, "writing memory debug info", err)
470 event.Log(ctx, fmt.Sprintf("Wrote memory usage debug info to %v", os.TempDir()))
476 func (i *Instance) writeMemoryDebug(threshold uint64, withNames bool) error {
477 suffix := "withnames"
482 filename := fmt.Sprintf("gopls.%d-%dGiB-%s.zip", os.Getpid(), threshold, suffix)
483 zipf, err := os.OpenFile(filepath.Join(os.TempDir(), filename), os.O_CREATE|os.O_RDWR, 0644)
487 zipw := zip.NewWriter(zipf)
489 f, err := zipw.Create("heap.pb.gz")
493 if err := rpprof.Lookup("heap").WriteTo(f, 0); err != nil {
497 f, err = zipw.Create("goroutines.txt")
501 if err := rpprof.Lookup("goroutine").WriteTo(f, 1); err != nil {
505 for _, cache := range i.State.Caches() {
506 cf, err := zipw.Create(fmt.Sprintf("cache-%v.html", cache.ID()))
510 if _, err := cf.Write([]byte(cache.PackageStats(withNames))); err != nil {
515 if err := zipw.Close(); err != nil {
521 func makeGlobalExporter(stderr io.Writer) event.Exporter {
522 p := export.Printer{}
524 return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
525 i := GetInstance(ctx)
528 // Don't log context cancellation errors.
529 if err := keys.Err.Get(ev); errors.Is(err, context.Canceled) {
532 // Make sure any log messages without an instance go to stderr.
535 p.WriteEvent(stderr, ev, lm)
538 level := log.LabeledLevel(lm)
539 // Exclude trace logs from LSP logs.
540 if level < log.Trace {
541 ctx = protocol.LogEvent(ctx, ev, lm, messageType(level))
547 return i.exporter(ctx, ev, lm)
551 func messageType(l log.Level) protocol.MessageType {
554 return protocol.Error
556 return protocol.Warning
563 func makeInstanceExporter(i *Instance) event.Exporter {
564 exporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
565 if i.ocagent != nil {
566 ctx = i.ocagent.ProcessEvent(ctx, ev, lm)
568 if i.prometheus != nil {
569 ctx = i.prometheus.ProcessEvent(ctx, ev, lm)
572 ctx = i.rpcs.ProcessEvent(ctx, ev, lm)
575 ctx = i.traces.ProcessEvent(ctx, ev, lm)
578 if s := cache.KeyCreateSession.Get(ev); s != nil {
581 if sid := tag.NewServer.Get(ev); sid != "" {
582 i.State.addServer(&Server{
584 Logfile: tag.Logfile.Get(ev),
585 DebugAddress: tag.DebugAddress.Get(ev),
586 GoplsPath: tag.GoplsPath.Get(ev),
587 ClientID: tag.ClientID.Get(ev),
590 if s := cache.KeyShutdownSession.Get(ev); s != nil {
591 i.State.dropClient(s)
593 if sid := tag.EndServer.Get(ev); sid != "" {
594 i.State.dropServer(sid)
596 if s := cache.KeyUpdateSession.Get(ev); s != nil {
597 if c := i.State.Client(s.ID()); c != nil {
598 c.DebugAddress = tag.DebugAddress.Get(ev)
599 c.Logfile = tag.Logfile.Get(ev)
600 c.ServerID = tag.ServerID.Get(ev)
601 c.GoplsPath = tag.GoplsPath.Get(ev)
607 // StdTrace must be above export.Spans below (by convention, export
608 // middleware applies its wrapped exporter last).
609 exporter = StdTrace(exporter)
610 metrics := metric.Config{}
611 registerMetrics(&metrics)
612 exporter = metrics.Exporter(exporter)
613 exporter = export.Spans(exporter)
614 exporter = export.Labels(exporter)
618 type dataFunc func(*http.Request) interface{}
620 func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) {
621 return func(w http.ResponseWriter, r *http.Request) {
626 if err := tmpl.Execute(w, data); err != nil {
627 event.Error(context.Background(), "", err)
628 http.Error(w, err.Error(), http.StatusInternalServerError)
633 func commas(s string) string {
634 for i := len(s); i > 3; {
636 s = s[:i] + "," + s[i:]
641 func fuint64(v uint64) string {
642 return commas(strconv.FormatUint(v, 10))
645 func fuint32(v uint32) string {
646 return commas(strconv.FormatUint(uint64(v), 10))
649 func fcontent(v []byte) string {
653 var BaseTemplate = template.Must(template.New("").Parse(`
656 <title>{{template "title" .}}</title>
659 display:inline-block;
666 list-style-type: none;
670 {{block "head" .}}{{end}}
674 <a href="/info">Info</a>
675 <a href="/memory">Memory</a>
676 <a href="/metrics">Metrics</a>
677 <a href="/rpc">RPC</a>
678 <a href="/trace">Trace</a>
680 <h1>{{template "title" .}}</h1>
687 {{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
688 {{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}}
689 {{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
690 {{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
691 {{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
692 {{define "filelink"}}<a href="/file/{{.Session}}/{{.FileIdentity.Hash}}">{{.FileIdentity.URI}}</a>{{end}}
693 `)).Funcs(template.FuncMap{
696 "fcontent": fcontent,
697 "localAddress": func(s string) string {
698 // Try to translate loopback addresses to localhost, both for cosmetics and
699 // because unspecified ipv6 addresses can break links on Windows.
701 // TODO(rfindley): In the future, it would be better not to assume the
702 // server is running on localhost, and instead construct this address using
704 host, port, err := net.SplitHostPort(s)
708 ip := net.ParseIP(host)
712 if ip.IsLoopback() || ip.IsUnspecified() {
713 return "localhost:" + port
717 "options": func(s *cache.Session) []string {
718 return showOptions(s.Options())
722 var MainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
723 {{define "title"}}GoPls server information{{end}}
726 <ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
728 <ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
730 <ul>{{range .State.Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
732 <ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul>
734 <ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
738 var InfoTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
739 {{define "title"}}GoPls version information{{end}}
745 var MemoryTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
746 {{define "title"}}GoPls memory usage{{end}}
747 {{define "head"}}<meta http-equiv="refresh" content="5">{{end}}
751 <tr><td class="label">Allocated bytes</td><td class="value">{{fuint64 .HeapAlloc}}</td></tr>
752 <tr><td class="label">Total allocated bytes</td><td class="value">{{fuint64 .TotalAlloc}}</td></tr>
753 <tr><td class="label">System bytes</td><td class="value">{{fuint64 .Sys}}</td></tr>
754 <tr><td class="label">Heap system bytes</td><td class="value">{{fuint64 .HeapSys}}</td></tr>
755 <tr><td class="label">Malloc calls</td><td class="value">{{fuint64 .Mallocs}}</td></tr>
756 <tr><td class="label">Frees</td><td class="value">{{fuint64 .Frees}}</td></tr>
757 <tr><td class="label">Idle heap bytes</td><td class="value">{{fuint64 .HeapIdle}}</td></tr>
758 <tr><td class="label">In use bytes</td><td class="value">{{fuint64 .HeapInuse}}</td></tr>
759 <tr><td class="label">Released to system bytes</td><td class="value">{{fuint64 .HeapReleased}}</td></tr>
760 <tr><td class="label">Heap object count</td><td class="value">{{fuint64 .HeapObjects}}</td></tr>
761 <tr><td class="label">Stack in use bytes</td><td class="value">{{fuint64 .StackInuse}}</td></tr>
762 <tr><td class="label">Stack from system bytes</td><td class="value">{{fuint64 .StackSys}}</td></tr>
763 <tr><td class="label">Bucket hash bytes</td><td class="value">{{fuint64 .BuckHashSys}}</td></tr>
764 <tr><td class="label">GC metadata bytes</td><td class="value">{{fuint64 .GCSys}}</td></tr>
765 <tr><td class="label">Off heap bytes</td><td class="value">{{fuint64 .OtherSys}}</td></tr>
769 <tr><th>Size</th><th>Mallocs</th><th>Frees</th></tr>
770 {{range .BySize}}<tr><td class="value">{{fuint32 .Size}}</td><td class="value">{{fuint64 .Mallocs}}</td><td class="value">{{fuint64 .Frees}}</td></tr>{{end}}
775 var DebugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
776 {{define "title"}}GoPls Debug pages{{end}}
778 <a href="/debug/pprof">Profiling</a>
782 var CacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
783 {{define "title"}}Cache {{.ID}}{{end}}
785 <h2>memoize.Store entries</h2>
786 <ul>{{range $k,$v := .MemStats}}<li>{{$k}} - {{$v}}</li>{{end}}</ul>
787 <h2>Per-package usage - not accurate, for guidance only</h2>
788 {{.PackageStats true}}
792 var ClientTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
793 {{define "title"}}Client {{.Session.ID}}{{end}}
795 Using session: <b>{{template "sessionlink" .Session.ID}}</b><br>
796 {{if .DebugAddress}}Debug this client at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
797 Logfile: {{.Logfile}}<br>
798 Gopls Path: {{.GoplsPath}}<br>
800 {{/*Service: []protocol.Server; each server has map[uri]fileReports;
801 each fileReport: map[diagnosticSoure]diagnosticReport
802 diagnosticSource is one of 5 source
803 diagnosticReport: snapshotID and map[hash]*source.Diagnostic
804 sourceDiagnostic: struct {
810 Severity protocol.DiagnosticSeverity
811 Tags []protocol.DiagnosticTag
813 Related []RelatedInformation
815 RelatedInformation: struct {
821 <ul>{{range $k, $v := .Service.Diagnostics}}<li>{{$k}}:<ol>{{range $v}}<li>{{.}}</li>{{end}}</ol></li>{{end}}</ul>
825 var ServerTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
826 {{define "title"}}Server {{.ID}}{{end}}
828 {{if .DebugAddress}}Debug this server at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
829 Logfile: {{.Logfile}}<br>
830 Gopls Path: {{.GoplsPath}}<br>
834 var SessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
835 {{define "title"}}Session {{.ID}}{{end}}
837 From: <b>{{template "cachelink" .Cache.ID}}</b><br>
839 <ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} in {{.Folder}}</li>{{end}}</ul>
841 <ul>{{range .Overlays}}<li>{{template "filelink" .}}</li>{{end}}</ul>
843 {{range options .}}<p>{{.}}{{end}}
847 var ViewTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
848 {{define "title"}}View {{.ID}}{{end}}
850 Name: <b>{{.Name}}</b><br>
851 Folder: <b>{{.Folder}}</b><br>
852 From: <b>{{template "sessionlink" .Session.ID}}</b><br>
854 <ul>{{range .Options.Env}}<li>{{.}}</li>{{end}}</ul>
858 var FileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
859 {{define "title"}}Overlay {{.FileIdentity.Hash}}{{end}}
862 From: <b>{{template "sessionlink" .Session}}</b><br>
863 URI: <b>{{.URI}}</b><br>
864 Identifier: <b>{{.FileIdentity.Hash}}</b><br>
865 Version: <b>{{.Version}}</b><br>
866 Kind: <b>{{.Kind}}</b><br>
869 <pre>{{fcontent .Read}}</pre>