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/tag"
38 "golang.org/x/tools/internal/lsp/protocol"
39 "golang.org/x/tools/internal/lsp/source"
40 errors "golang.org/x/xerrors"
43 type instanceKeyType int
45 const instanceKey = instanceKeyType(0)
47 // An Instance holds all debug information associated with a gopls instance.
48 type Instance struct {
53 ListenedDebugAddress string
59 exporter event.Exporter
61 ocagent *ocagent.Exporter
62 prometheus *prometheus.Exporter
68 // State holds debugging information related to the server state.
75 // Caches returns the set of Cache objects currently being served.
76 func (st *State) Caches() []*cache.Cache {
77 var caches []*cache.Cache
78 seen := make(map[string]struct{})
79 for _, client := range st.Clients() {
80 cache, ok := client.Session.Cache().(*cache.Cache)
84 if _, found := seen[cache.ID()]; found {
87 seen[cache.ID()] = struct{}{}
88 caches = append(caches, cache)
93 // Cache returns the Cache that matches the supplied id.
94 func (st *State) Cache(id string) *cache.Cache {
95 for _, c := range st.Caches() {
103 // Sessions returns the set of Session objects currently being served.
104 func (st *State) Sessions() []*cache.Session {
105 var sessions []*cache.Session
106 for _, client := range st.Clients() {
107 sessions = append(sessions, client.Session)
112 // Session returns the Session that matches the supplied id.
113 func (st *State) Session(id string) *cache.Session {
114 for _, s := range st.Sessions() {
122 // Views returns the set of View objects currently being served.
123 func (st *State) Views() []*cache.View {
124 var views []*cache.View
125 for _, s := range st.Sessions() {
126 for _, v := range s.Views() {
127 if cv, ok := v.(*cache.View); ok {
128 views = append(views, cv)
135 // View returns the View that matches the supplied id.
136 func (st *State) View(id string) *cache.View {
137 for _, v := range st.Views() {
145 // Clients returns the set of Clients currently being served.
146 func (st *State) Clients() []*Client {
149 clients := make([]*Client, len(st.clients))
150 copy(clients, st.clients)
154 // Client returns the Client matching the supplied id.
155 func (st *State) Client(id string) *Client {
156 for _, c := range st.Clients() {
157 if c.Session.ID() == id {
164 // Servers returns the set of Servers the instance is currently connected to.
165 func (st *State) Servers() []*Server {
168 servers := make([]*Server, len(st.servers))
169 copy(servers, st.servers)
173 // A Client is an incoming connection from a remote client.
175 Session *cache.Session
182 // A Server is an outgoing connection to a remote LSP server.
191 // AddClient adds a client to the set being served.
192 func (st *State) addClient(session *cache.Session) {
195 st.clients = append(st.clients, &Client{Session: session})
198 // DropClient removes a client from the set being served.
199 func (st *State) dropClient(session source.Session) {
202 for i, c := range st.clients {
203 if c.Session == session {
204 copy(st.clients[i:], st.clients[i+1:])
205 st.clients[len(st.clients)-1] = nil
206 st.clients = st.clients[:len(st.clients)-1]
212 // AddServer adds a server to the set being queried. In practice, there should
213 // be at most one remote server.
214 func (st *State) addServer(server *Server) {
217 st.servers = append(st.servers, server)
220 // DropServer drops a server from the set being queried.
221 func (st *State) dropServer(id string) {
224 for i, s := range st.servers {
226 copy(st.servers[i:], st.servers[i+1:])
227 st.servers[len(st.servers)-1] = nil
228 st.servers = st.servers[:len(st.servers)-1]
234 func (i *Instance) getCache(r *http.Request) interface{} {
235 return i.State.Cache(path.Base(r.URL.Path))
238 func (i *Instance) getSession(r *http.Request) interface{} {
239 return i.State.Session(path.Base(r.URL.Path))
242 func (i Instance) getClient(r *http.Request) interface{} {
243 return i.State.Client(path.Base(r.URL.Path))
246 func (i Instance) getServer(r *http.Request) interface{} {
248 defer i.State.mu.Unlock()
249 id := path.Base(r.URL.Path)
250 for _, s := range i.State.servers {
258 func (i Instance) getView(r *http.Request) interface{} {
259 return i.State.View(path.Base(r.URL.Path))
262 func (i *Instance) getFile(r *http.Request) interface{} {
263 identifier := path.Base(r.URL.Path)
264 sid := path.Base(path.Dir(r.URL.Path))
265 s := i.State.Session(sid)
269 for _, o := range s.Overlays() {
270 if o.FileIdentity().Hash == identifier {
277 func (i *Instance) getInfo(r *http.Request) interface{} {
278 buf := &bytes.Buffer{}
279 i.PrintServerInfo(r.Context(), buf)
280 return template.HTML(buf.String())
283 func getMemory(r *http.Request) interface{} {
284 var m runtime.MemStats
285 runtime.ReadMemStats(&m)
290 event.SetExporter(makeGlobalExporter(os.Stderr))
293 func GetInstance(ctx context.Context) *Instance {
297 v := ctx.Value(instanceKey)
304 // WithInstance creates debug instance ready for use using the supplied
305 // configuration and stores it in the returned context.
306 func WithInstance(ctx context.Context, workdir, agent string) context.Context {
308 StartTime: time.Now(),
310 OCAgentConfig: agent,
312 i.LogWriter = os.Stderr
313 ocConfig := ocagent.Discover()
314 //TODO: we should not need to adjust the discovered configuration
315 ocConfig.Address = i.OCAgentConfig
316 i.ocagent = ocagent.Connect(ocConfig)
317 i.prometheus = prometheus.New()
321 i.exporter = makeInstanceExporter(i)
322 return context.WithValue(ctx, instanceKey, i)
325 // SetLogFile sets the logfile for use with this instance.
326 func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) {
327 // TODO: probably a better solution for deferring closure to the caller would
328 // be for the debug instance to itself be closed, but this fixes the
329 // immediate bug of logs not being captured.
330 closeLog := func() {}
332 if logfile == "auto" {
334 logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-daemon-%d.log", os.Getpid()))
336 logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
339 f, err := os.Create(logfile)
341 return nil, errors.Errorf("unable to create log file: %w", err)
346 log.SetOutput(io.MultiWriter(os.Stderr, f))
353 // Serve starts and runs a debug server in the background.
354 // It also logs the port the server starts on, to allow for :0 auto assigned
356 func (i *Instance) Serve(ctx context.Context) error {
357 if i.DebugAddress == "" {
360 listener, err := net.Listen("tcp", i.DebugAddress)
364 i.ListenedDebugAddress = listener.Addr().String()
366 port := listener.Addr().(*net.TCPAddr).Port
367 if strings.HasSuffix(i.DebugAddress, ":0") {
368 log.Printf("debug server listening on port %d", port)
370 event.Log(ctx, "Debug serving", tag.Port.Of(port))
372 mux := http.NewServeMux()
373 mux.HandleFunc("/", render(mainTmpl, func(*http.Request) interface{} { return i }))
374 mux.HandleFunc("/debug/", render(debugTmpl, nil))
375 mux.HandleFunc("/debug/pprof/", pprof.Index)
376 mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
377 mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
378 mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
379 mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
380 if i.prometheus != nil {
381 mux.HandleFunc("/metrics/", i.prometheus.Serve)
384 mux.HandleFunc("/rpc/", render(rpcTmpl, i.rpcs.getData))
387 mux.HandleFunc("/trace/", render(traceTmpl, i.traces.getData))
389 mux.HandleFunc("/cache/", render(cacheTmpl, i.getCache))
390 mux.HandleFunc("/session/", render(sessionTmpl, i.getSession))
391 mux.HandleFunc("/view/", render(viewTmpl, i.getView))
392 mux.HandleFunc("/client/", render(clientTmpl, i.getClient))
393 mux.HandleFunc("/server/", render(serverTmpl, i.getServer))
394 mux.HandleFunc("/file/", render(fileTmpl, i.getFile))
395 mux.HandleFunc("/info", render(infoTmpl, i.getInfo))
396 mux.HandleFunc("/memory", render(memoryTmpl, getMemory))
397 if err := http.Serve(listener, mux); err != nil {
398 event.Error(ctx, "Debug server failed", err)
401 event.Log(ctx, "Debug server finished")
406 // MonitorMemory starts recording memory statistics each second.
407 func (i *Instance) MonitorMemory(ctx context.Context) {
408 tick := time.NewTicker(time.Second)
409 nextThresholdGiB := uint64(1)
413 var mem runtime.MemStats
414 runtime.ReadMemStats(&mem)
415 if mem.HeapAlloc < nextThresholdGiB*1<<30 {
418 if err := i.writeMemoryDebug(nextThresholdGiB, true); err != nil {
419 event.Error(ctx, "writing memory debug info", err)
421 if err := i.writeMemoryDebug(nextThresholdGiB, false); err != nil {
422 event.Error(ctx, "writing memory debug info", err)
424 event.Log(ctx, fmt.Sprintf("Wrote memory usage debug info to %v", os.TempDir()))
430 func (i *Instance) writeMemoryDebug(threshold uint64, withNames bool) error {
431 suffix := "withnames"
436 filename := fmt.Sprintf("gopls.%d-%dGiB-%s.zip", os.Getpid(), threshold, suffix)
437 zipf, err := os.OpenFile(filepath.Join(os.TempDir(), filename), os.O_CREATE|os.O_RDWR, 0644)
441 zipw := zip.NewWriter(zipf)
443 f, err := zipw.Create("heap.pb.gz")
447 if err := rpprof.Lookup("heap").WriteTo(f, 0); err != nil {
451 f, err = zipw.Create("goroutines.txt")
455 if err := rpprof.Lookup("goroutine").WriteTo(f, 1); err != nil {
459 for _, cache := range i.State.Caches() {
460 cf, err := zipw.Create(fmt.Sprintf("cache-%v.html", cache.ID()))
464 if _, err := cf.Write([]byte(cache.PackageStats(withNames))); err != nil {
469 if err := zipw.Close(); err != nil {
475 func makeGlobalExporter(stderr io.Writer) event.Exporter {
476 p := export.Printer{}
478 return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
479 i := GetInstance(ctx)
482 // Don't log context cancellation errors.
483 if err := keys.Err.Get(ev); errors.Is(err, context.Canceled) {
486 // Make sure any log messages without an instance go to stderr.
489 p.WriteEvent(stderr, ev, lm)
493 ctx = protocol.LogEvent(ctx, ev, lm)
497 return i.exporter(ctx, ev, lm)
501 func makeInstanceExporter(i *Instance) event.Exporter {
502 exporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
503 if i.ocagent != nil {
504 ctx = i.ocagent.ProcessEvent(ctx, ev, lm)
506 if i.prometheus != nil {
507 ctx = i.prometheus.ProcessEvent(ctx, ev, lm)
510 ctx = i.rpcs.ProcessEvent(ctx, ev, lm)
513 ctx = i.traces.ProcessEvent(ctx, ev, lm)
516 if s := cache.KeyCreateSession.Get(ev); s != nil {
519 if sid := tag.NewServer.Get(ev); sid != "" {
520 i.State.addServer(&Server{
522 Logfile: tag.Logfile.Get(ev),
523 DebugAddress: tag.DebugAddress.Get(ev),
524 GoplsPath: tag.GoplsPath.Get(ev),
525 ClientID: tag.ClientID.Get(ev),
528 if s := cache.KeyShutdownSession.Get(ev); s != nil {
529 i.State.dropClient(s)
531 if sid := tag.EndServer.Get(ev); sid != "" {
532 i.State.dropServer(sid)
534 if s := cache.KeyUpdateSession.Get(ev); s != nil {
535 if c := i.State.Client(s.ID()); c != nil {
536 c.DebugAddress = tag.DebugAddress.Get(ev)
537 c.Logfile = tag.Logfile.Get(ev)
538 c.ServerID = tag.ServerID.Get(ev)
539 c.GoplsPath = tag.GoplsPath.Get(ev)
545 metrics := metric.Config{}
546 registerMetrics(&metrics)
547 exporter = metrics.Exporter(exporter)
548 exporter = export.Spans(exporter)
549 exporter = export.Labels(exporter)
553 type dataFunc func(*http.Request) interface{}
555 func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) {
556 return func(w http.ResponseWriter, r *http.Request) {
561 if err := tmpl.Execute(w, data); err != nil {
562 event.Error(context.Background(), "", err)
563 http.Error(w, err.Error(), http.StatusInternalServerError)
568 func commas(s string) string {
569 for i := len(s); i > 3; {
571 s = s[:i] + "," + s[i:]
576 func fuint64(v uint64) string {
577 return commas(strconv.FormatUint(v, 10))
580 func fuint32(v uint32) string {
581 return commas(strconv.FormatUint(uint64(v), 10))
584 func fcontent(v []byte) string {
588 var baseTemplate = template.Must(template.New("").Parse(`
591 <title>{{template "title" .}}</title>
594 display:inline-block;
601 list-style-type: none;
605 {{block "head" .}}{{end}}
609 <a href="/info">Info</a>
610 <a href="/memory">Memory</a>
611 <a href="/metrics">Metrics</a>
612 <a href="/rpc">RPC</a>
613 <a href="/trace">Trace</a>
615 <h1>{{template "title" .}}</h1>
622 {{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
623 {{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}}
624 {{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
625 {{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
626 {{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
627 {{define "filelink"}}<a href="/file/{{.SessionID}}/{{.Identifier}}">{{.URI}}</a>{{end}}
628 `)).Funcs(template.FuncMap{
631 "fcontent": fcontent,
632 "localAddress": func(s string) string {
633 // Try to translate loopback addresses to localhost, both for cosmetics and
634 // because unspecified ipv6 addresses can break links on Windows.
636 // TODO(rfindley): In the future, it would be better not to assume the
637 // server is running on localhost, and instead construct this address using
639 host, port, err := net.SplitHostPort(s)
643 ip := net.ParseIP(host)
647 if ip.IsLoopback() || ip.IsUnspecified() {
648 return "localhost:" + port
654 var mainTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
655 {{define "title"}}GoPls server information{{end}}
658 <ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
660 <ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
662 <ul>{{range .State.Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
664 <ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul>
666 <ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
670 var infoTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
671 {{define "title"}}GoPls version information{{end}}
677 var memoryTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
678 {{define "title"}}GoPls memory usage{{end}}
679 {{define "head"}}<meta http-equiv="refresh" content="5">{{end}}
683 <tr><td class="label">Allocated bytes</td><td class="value">{{fuint64 .HeapAlloc}}</td></tr>
684 <tr><td class="label">Total allocated bytes</td><td class="value">{{fuint64 .TotalAlloc}}</td></tr>
685 <tr><td class="label">System bytes</td><td class="value">{{fuint64 .Sys}}</td></tr>
686 <tr><td class="label">Heap system bytes</td><td class="value">{{fuint64 .HeapSys}}</td></tr>
687 <tr><td class="label">Malloc calls</td><td class="value">{{fuint64 .Mallocs}}</td></tr>
688 <tr><td class="label">Frees</td><td class="value">{{fuint64 .Frees}}</td></tr>
689 <tr><td class="label">Idle heap bytes</td><td class="value">{{fuint64 .HeapIdle}}</td></tr>
690 <tr><td class="label">In use bytes</td><td class="value">{{fuint64 .HeapInuse}}</td></tr>
691 <tr><td class="label">Released to system bytes</td><td class="value">{{fuint64 .HeapReleased}}</td></tr>
692 <tr><td class="label">Heap object count</td><td class="value">{{fuint64 .HeapObjects}}</td></tr>
693 <tr><td class="label">Stack in use bytes</td><td class="value">{{fuint64 .StackInuse}}</td></tr>
694 <tr><td class="label">Stack from system bytes</td><td class="value">{{fuint64 .StackSys}}</td></tr>
695 <tr><td class="label">Bucket hash bytes</td><td class="value">{{fuint64 .BuckHashSys}}</td></tr>
696 <tr><td class="label">GC metadata bytes</td><td class="value">{{fuint64 .GCSys}}</td></tr>
697 <tr><td class="label">Off heap bytes</td><td class="value">{{fuint64 .OtherSys}}</td></tr>
701 <tr><th>Size</th><th>Mallocs</th><th>Frees</th></tr>
702 {{range .BySize}}<tr><td class="value">{{fuint32 .Size}}</td><td class="value">{{fuint64 .Mallocs}}</td><td class="value">{{fuint64 .Frees}}</td></tr>{{end}}
707 var debugTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
708 {{define "title"}}GoPls Debug pages{{end}}
710 <a href="/debug/pprof">Profiling</a>
714 var cacheTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
715 {{define "title"}}Cache {{.ID}}{{end}}
717 <h2>memoize.Store entries</h2>
718 <ul>{{range $k,$v := .MemStats}}<li>{{$k}} - {{$v}}</li>{{end}}</ul>
719 <h2>Per-package usage - not accurate, for guidance only</h2>
720 {{.PackageStats true}}
724 var clientTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
725 {{define "title"}}Client {{.Session.ID}}{{end}}
727 Using session: <b>{{template "sessionlink" .Session.ID}}</b><br>
728 {{if .DebugAddress}}Debug this client at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
729 Logfile: {{.Logfile}}<br>
730 Gopls Path: {{.GoplsPath}}<br>
734 var serverTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
735 {{define "title"}}Server {{.ID}}{{end}}
737 {{if .DebugAddress}}Debug this server at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
738 Logfile: {{.Logfile}}<br>
739 Gopls Path: {{.GoplsPath}}<br>
743 var sessionTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
744 {{define "title"}}Session {{.ID}}{{end}}
746 From: <b>{{template "cachelink" .Cache.ID}}</b><br>
748 <ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} in {{.Folder}}</li>{{end}}</ul>
750 <ul>{{range .Overlays}}<li>{{template "filelink" .Identity}}</li>{{end}}</ul>
754 var viewTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
755 {{define "title"}}View {{.ID}}{{end}}
757 Name: <b>{{.Name}}</b><br>
758 Folder: <b>{{.Folder}}</b><br>
759 From: <b>{{template "sessionlink" .Session.ID}}</b><br>
761 <ul>{{range .Options.Env}}<li>{{.}}</li>{{end}}</ul>
765 var fileTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
766 {{define "title"}}Overlay {{.Identity.Identifier}}{{end}}
769 From: <b>{{template "sessionlink" .SessionID}}</b><br>
770 URI: <b>{{.URI}}</b><br>
771 Identifier: <b>{{.Identifier}}</b><br>
772 Version: <b>{{.Version}}</b><br>
773 Kind: <b>{{.Kind}}</b><br>
776 <pre>{{fcontent .Data}}</pre>