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
186 // A Server is an outgoing connection to a remote LSP server.
195 // AddClient adds a client to the set being served.
196 func (st *State) addClient(session *cache.Session) {
199 st.clients = append(st.clients, &Client{Session: session})
202 // DropClient removes a client from the set being served.
203 func (st *State) dropClient(session source.Session) {
206 for i, c := range st.clients {
207 if c.Session == session {
208 copy(st.clients[i:], st.clients[i+1:])
209 st.clients[len(st.clients)-1] = nil
210 st.clients = st.clients[:len(st.clients)-1]
216 // AddServer adds a server to the set being queried. In practice, there should
217 // be at most one remote server.
218 func (st *State) addServer(server *Server) {
221 st.servers = append(st.servers, server)
224 // DropServer drops a server from the set being queried.
225 func (st *State) dropServer(id string) {
228 for i, s := range st.servers {
230 copy(st.servers[i:], st.servers[i+1:])
231 st.servers[len(st.servers)-1] = nil
232 st.servers = st.servers[:len(st.servers)-1]
238 // an http.ResponseWriter that filters writes
239 type filterResponse struct {
240 w http.ResponseWriter
241 edit func([]byte) []byte
244 func (c filterResponse) Header() http.Header {
248 func (c filterResponse) Write(buf []byte) (int, error) {
250 return c.w.Write(ans)
253 func (c filterResponse) WriteHeader(n int) {
257 // replace annoying nuls by spaces
258 func cmdline(w http.ResponseWriter, r *http.Request) {
259 fake := filterResponse{
261 edit: func(buf []byte) []byte {
262 return bytes.ReplaceAll(buf, []byte{0}, []byte{' '})
265 pprof.Cmdline(fake, r)
268 func (i *Instance) getCache(r *http.Request) interface{} {
269 return i.State.Cache(path.Base(r.URL.Path))
272 func (i *Instance) getSession(r *http.Request) interface{} {
273 return i.State.Session(path.Base(r.URL.Path))
276 func (i Instance) getClient(r *http.Request) interface{} {
277 return i.State.Client(path.Base(r.URL.Path))
280 func (i Instance) getServer(r *http.Request) interface{} {
282 defer i.State.mu.Unlock()
283 id := path.Base(r.URL.Path)
284 for _, s := range i.State.servers {
292 func (i Instance) getView(r *http.Request) interface{} {
293 return i.State.View(path.Base(r.URL.Path))
296 func (i *Instance) getFile(r *http.Request) interface{} {
297 identifier := path.Base(r.URL.Path)
298 sid := path.Base(path.Dir(r.URL.Path))
299 s := i.State.Session(sid)
303 for _, o := range s.Overlays() {
304 if o.FileIdentity().Hash == identifier {
311 func (i *Instance) getInfo(r *http.Request) interface{} {
312 buf := &bytes.Buffer{}
313 i.PrintServerInfo(r.Context(), buf)
314 return template.HTML(buf.String())
317 func getMemory(r *http.Request) interface{} {
318 var m runtime.MemStats
319 runtime.ReadMemStats(&m)
324 event.SetExporter(makeGlobalExporter(os.Stderr))
327 func GetInstance(ctx context.Context) *Instance {
331 v := ctx.Value(instanceKey)
338 // WithInstance creates debug instance ready for use using the supplied
339 // configuration and stores it in the returned context.
340 func WithInstance(ctx context.Context, workdir, agent string) context.Context {
342 StartTime: time.Now(),
344 OCAgentConfig: agent,
346 i.LogWriter = os.Stderr
347 ocConfig := ocagent.Discover()
348 //TODO: we should not need to adjust the discovered configuration
349 ocConfig.Address = i.OCAgentConfig
350 i.ocagent = ocagent.Connect(ocConfig)
351 i.prometheus = prometheus.New()
355 i.exporter = makeInstanceExporter(i)
356 return context.WithValue(ctx, instanceKey, i)
359 // SetLogFile sets the logfile for use with this instance.
360 func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) {
361 // TODO: probably a better solution for deferring closure to the caller would
362 // be for the debug instance to itself be closed, but this fixes the
363 // immediate bug of logs not being captured.
364 closeLog := func() {}
366 if logfile == "auto" {
368 logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-daemon-%d.log", os.Getpid()))
370 logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
373 f, err := os.Create(logfile)
375 return nil, errors.Errorf("unable to create log file: %w", err)
380 stdlog.SetOutput(io.MultiWriter(os.Stderr, f))
387 // Serve starts and runs a debug server in the background.
388 // It also logs the port the server starts on, to allow for :0 auto assigned
390 func (i *Instance) Serve(ctx context.Context) error {
391 stdlog.SetFlags(stdlog.Lshortfile)
392 if i.DebugAddress == "" {
395 listener, err := net.Listen("tcp", i.DebugAddress)
399 i.ListenedDebugAddress = listener.Addr().String()
401 port := listener.Addr().(*net.TCPAddr).Port
402 if strings.HasSuffix(i.DebugAddress, ":0") {
403 stdlog.Printf("debug server listening at http://localhost:%d", port)
405 event.Log(ctx, "Debug serving", tag.Port.Of(port))
407 mux := http.NewServeMux()
408 mux.HandleFunc("/", render(MainTmpl, func(*http.Request) interface{} { return i }))
409 mux.HandleFunc("/debug/", render(DebugTmpl, nil))
410 mux.HandleFunc("/debug/pprof/", pprof.Index)
411 mux.HandleFunc("/debug/pprof/cmdline", cmdline)
412 mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
413 mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
414 mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
415 if i.prometheus != nil {
416 mux.HandleFunc("/metrics/", i.prometheus.Serve)
419 mux.HandleFunc("/rpc/", render(RPCTmpl, i.rpcs.getData))
422 mux.HandleFunc("/trace/", render(TraceTmpl, i.traces.getData))
424 mux.HandleFunc("/cache/", render(CacheTmpl, i.getCache))
425 mux.HandleFunc("/session/", render(SessionTmpl, i.getSession))
426 mux.HandleFunc("/view/", render(ViewTmpl, i.getView))
427 mux.HandleFunc("/client/", render(ClientTmpl, i.getClient))
428 mux.HandleFunc("/server/", render(ServerTmpl, i.getServer))
429 mux.HandleFunc("/file/", render(FileTmpl, i.getFile))
430 mux.HandleFunc("/info", render(InfoTmpl, i.getInfo))
431 mux.HandleFunc("/memory", render(MemoryTmpl, getMemory))
432 if err := http.Serve(listener, mux); err != nil {
433 event.Error(ctx, "Debug server failed", err)
436 event.Log(ctx, "Debug server finished")
441 // MonitorMemory starts recording memory statistics each second.
442 func (i *Instance) MonitorMemory(ctx context.Context) {
443 tick := time.NewTicker(time.Second)
444 nextThresholdGiB := uint64(1)
448 var mem runtime.MemStats
449 runtime.ReadMemStats(&mem)
450 if mem.HeapAlloc < nextThresholdGiB*1<<30 {
453 if err := i.writeMemoryDebug(nextThresholdGiB, true); err != nil {
454 event.Error(ctx, "writing memory debug info", err)
456 if err := i.writeMemoryDebug(nextThresholdGiB, false); err != nil {
457 event.Error(ctx, "writing memory debug info", err)
459 event.Log(ctx, fmt.Sprintf("Wrote memory usage debug info to %v", os.TempDir()))
465 func (i *Instance) writeMemoryDebug(threshold uint64, withNames bool) error {
466 suffix := "withnames"
471 filename := fmt.Sprintf("gopls.%d-%dGiB-%s.zip", os.Getpid(), threshold, suffix)
472 zipf, err := os.OpenFile(filepath.Join(os.TempDir(), filename), os.O_CREATE|os.O_RDWR, 0644)
476 zipw := zip.NewWriter(zipf)
478 f, err := zipw.Create("heap.pb.gz")
482 if err := rpprof.Lookup("heap").WriteTo(f, 0); err != nil {
486 f, err = zipw.Create("goroutines.txt")
490 if err := rpprof.Lookup("goroutine").WriteTo(f, 1); err != nil {
494 for _, cache := range i.State.Caches() {
495 cf, err := zipw.Create(fmt.Sprintf("cache-%v.html", cache.ID()))
499 if _, err := cf.Write([]byte(cache.PackageStats(withNames))); err != nil {
504 if err := zipw.Close(); err != nil {
510 func makeGlobalExporter(stderr io.Writer) event.Exporter {
511 p := export.Printer{}
513 return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
514 i := GetInstance(ctx)
517 // Don't log context cancellation errors.
518 if err := keys.Err.Get(ev); errors.Is(err, context.Canceled) {
521 // Make sure any log messages without an instance go to stderr.
524 p.WriteEvent(stderr, ev, lm)
527 level := log.LabeledLevel(lm)
528 // Exclude trace logs from LSP logs.
529 if level < log.Trace {
530 ctx = protocol.LogEvent(ctx, ev, lm, messageType(level))
536 return i.exporter(ctx, ev, lm)
540 func messageType(l log.Level) protocol.MessageType {
543 return protocol.Error
545 return protocol.Warning
552 func makeInstanceExporter(i *Instance) event.Exporter {
553 exporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
554 if i.ocagent != nil {
555 ctx = i.ocagent.ProcessEvent(ctx, ev, lm)
557 if i.prometheus != nil {
558 ctx = i.prometheus.ProcessEvent(ctx, ev, lm)
561 ctx = i.rpcs.ProcessEvent(ctx, ev, lm)
564 ctx = i.traces.ProcessEvent(ctx, ev, lm)
567 if s := cache.KeyCreateSession.Get(ev); s != nil {
570 if sid := tag.NewServer.Get(ev); sid != "" {
571 i.State.addServer(&Server{
573 Logfile: tag.Logfile.Get(ev),
574 DebugAddress: tag.DebugAddress.Get(ev),
575 GoplsPath: tag.GoplsPath.Get(ev),
576 ClientID: tag.ClientID.Get(ev),
579 if s := cache.KeyShutdownSession.Get(ev); s != nil {
580 i.State.dropClient(s)
582 if sid := tag.EndServer.Get(ev); sid != "" {
583 i.State.dropServer(sid)
585 if s := cache.KeyUpdateSession.Get(ev); s != nil {
586 if c := i.State.Client(s.ID()); c != nil {
587 c.DebugAddress = tag.DebugAddress.Get(ev)
588 c.Logfile = tag.Logfile.Get(ev)
589 c.ServerID = tag.ServerID.Get(ev)
590 c.GoplsPath = tag.GoplsPath.Get(ev)
596 // StdTrace must be above export.Spans below (by convention, export
597 // middleware applies its wrapped exporter last).
598 exporter = StdTrace(exporter)
599 metrics := metric.Config{}
600 registerMetrics(&metrics)
601 exporter = metrics.Exporter(exporter)
602 exporter = export.Spans(exporter)
603 exporter = export.Labels(exporter)
607 type dataFunc func(*http.Request) interface{}
609 func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) {
610 return func(w http.ResponseWriter, r *http.Request) {
615 if err := tmpl.Execute(w, data); err != nil {
616 event.Error(context.Background(), "", err)
617 http.Error(w, err.Error(), http.StatusInternalServerError)
622 func commas(s string) string {
623 for i := len(s); i > 3; {
625 s = s[:i] + "," + s[i:]
630 func fuint64(v uint64) string {
631 return commas(strconv.FormatUint(v, 10))
634 func fuint32(v uint32) string {
635 return commas(strconv.FormatUint(uint64(v), 10))
638 func fcontent(v []byte) string {
642 var BaseTemplate = template.Must(template.New("").Parse(`
645 <title>{{template "title" .}}</title>
648 display:inline-block;
655 list-style-type: none;
659 {{block "head" .}}{{end}}
663 <a href="/info">Info</a>
664 <a href="/memory">Memory</a>
665 <a href="/metrics">Metrics</a>
666 <a href="/rpc">RPC</a>
667 <a href="/trace">Trace</a>
669 <h1>{{template "title" .}}</h1>
676 {{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
677 {{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}}
678 {{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
679 {{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
680 {{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
681 {{define "filelink"}}<a href="/file/{{.Session}}/{{.FileIdentity.Hash}}">{{.FileIdentity.URI}}</a>{{end}}
682 `)).Funcs(template.FuncMap{
685 "fcontent": fcontent,
686 "localAddress": func(s string) string {
687 // Try to translate loopback addresses to localhost, both for cosmetics and
688 // because unspecified ipv6 addresses can break links on Windows.
690 // TODO(rfindley): In the future, it would be better not to assume the
691 // server is running on localhost, and instead construct this address using
693 host, port, err := net.SplitHostPort(s)
697 ip := net.ParseIP(host)
701 if ip.IsLoopback() || ip.IsUnspecified() {
702 return "localhost:" + port
706 "options": func(s *cache.Session) []string {
707 return showOptions(s.Options())
711 var MainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
712 {{define "title"}}GoPls server information{{end}}
715 <ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
717 <ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
719 <ul>{{range .State.Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
721 <ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul>
723 <ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
727 var InfoTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
728 {{define "title"}}GoPls version information{{end}}
734 var MemoryTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
735 {{define "title"}}GoPls memory usage{{end}}
736 {{define "head"}}<meta http-equiv="refresh" content="5">{{end}}
740 <tr><td class="label">Allocated bytes</td><td class="value">{{fuint64 .HeapAlloc}}</td></tr>
741 <tr><td class="label">Total allocated bytes</td><td class="value">{{fuint64 .TotalAlloc}}</td></tr>
742 <tr><td class="label">System bytes</td><td class="value">{{fuint64 .Sys}}</td></tr>
743 <tr><td class="label">Heap system bytes</td><td class="value">{{fuint64 .HeapSys}}</td></tr>
744 <tr><td class="label">Malloc calls</td><td class="value">{{fuint64 .Mallocs}}</td></tr>
745 <tr><td class="label">Frees</td><td class="value">{{fuint64 .Frees}}</td></tr>
746 <tr><td class="label">Idle heap bytes</td><td class="value">{{fuint64 .HeapIdle}}</td></tr>
747 <tr><td class="label">In use bytes</td><td class="value">{{fuint64 .HeapInuse}}</td></tr>
748 <tr><td class="label">Released to system bytes</td><td class="value">{{fuint64 .HeapReleased}}</td></tr>
749 <tr><td class="label">Heap object count</td><td class="value">{{fuint64 .HeapObjects}}</td></tr>
750 <tr><td class="label">Stack in use bytes</td><td class="value">{{fuint64 .StackInuse}}</td></tr>
751 <tr><td class="label">Stack from system bytes</td><td class="value">{{fuint64 .StackSys}}</td></tr>
752 <tr><td class="label">Bucket hash bytes</td><td class="value">{{fuint64 .BuckHashSys}}</td></tr>
753 <tr><td class="label">GC metadata bytes</td><td class="value">{{fuint64 .GCSys}}</td></tr>
754 <tr><td class="label">Off heap bytes</td><td class="value">{{fuint64 .OtherSys}}</td></tr>
758 <tr><th>Size</th><th>Mallocs</th><th>Frees</th></tr>
759 {{range .BySize}}<tr><td class="value">{{fuint32 .Size}}</td><td class="value">{{fuint64 .Mallocs}}</td><td class="value">{{fuint64 .Frees}}</td></tr>{{end}}
764 var DebugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
765 {{define "title"}}GoPls Debug pages{{end}}
767 <a href="/debug/pprof">Profiling</a>
771 var CacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
772 {{define "title"}}Cache {{.ID}}{{end}}
774 <h2>memoize.Store entries</h2>
775 <ul>{{range $k,$v := .MemStats}}<li>{{$k}} - {{$v}}</li>{{end}}</ul>
776 <h2>Per-package usage - not accurate, for guidance only</h2>
777 {{.PackageStats true}}
781 var ClientTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
782 {{define "title"}}Client {{.Session.ID}}{{end}}
784 Using session: <b>{{template "sessionlink" .Session.ID}}</b><br>
785 {{if .DebugAddress}}Debug this client at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
786 Logfile: {{.Logfile}}<br>
787 Gopls Path: {{.GoplsPath}}<br>
791 var ServerTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
792 {{define "title"}}Server {{.ID}}{{end}}
794 {{if .DebugAddress}}Debug this server at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
795 Logfile: {{.Logfile}}<br>
796 Gopls Path: {{.GoplsPath}}<br>
800 var SessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
801 {{define "title"}}Session {{.ID}}{{end}}
803 From: <b>{{template "cachelink" .Cache.ID}}</b><br>
805 <ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} in {{.Folder}}</li>{{end}}</ul>
807 <ul>{{range .Overlays}}<li>{{template "filelink" .}}</li>{{end}}</ul>
809 {{range options .}}<p>{{.}}{{end}}
813 var ViewTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
814 {{define "title"}}View {{.ID}}{{end}}
816 Name: <b>{{.Name}}</b><br>
817 Folder: <b>{{.Folder}}</b><br>
818 From: <b>{{template "sessionlink" .Session.ID}}</b><br>
820 <ul>{{range .Options.Env}}<li>{{.}}</li>{{end}}</ul>
824 var FileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
825 {{define "title"}}Overlay {{.FileIdentity.Hash}}{{end}}
828 From: <b>{{template "sessionlink" .Session}}</b><br>
829 URI: <b>{{.URI}}</b><br>
830 Identifier: <b>{{.FileIdentity.Hash}}</b><br>
831 Version: <b>{{.Version}}</b><br>
832 Kind: <b>{{.Kind}}</b><br>
835 <pre>{{fcontent .Read}}</pre>