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.
4
5package debug
6
7import (
8	"archive/zip"
9	"bytes"
10	"context"
11	"fmt"
12	"html/template"
13	"io"
14	stdlog "log"
15	"net"
16	"net/http"
17	"net/http/pprof"
18	"os"
19	"path"
20	"path/filepath"
21	"runtime"
22	rpprof "runtime/pprof"
23	"sort"
24	"strconv"
25	"strings"
26	"sync"
27	"time"
28
29	"golang.org/x/tools/internal/event"
30	"golang.org/x/tools/internal/event/core"
31	"golang.org/x/tools/internal/event/export"
32	"golang.org/x/tools/internal/event/export/metric"
33	"golang.org/x/tools/internal/event/export/ocagent"
34	"golang.org/x/tools/internal/event/export/prometheus"
35	"golang.org/x/tools/internal/event/keys"
36	"golang.org/x/tools/internal/event/label"
37	"golang.org/x/tools/internal/lsp/cache"
38	"golang.org/x/tools/internal/lsp/debug/log"
39	"golang.org/x/tools/internal/lsp/debug/tag"
40	"golang.org/x/tools/internal/lsp/protocol"
41	"golang.org/x/tools/internal/lsp/source"
42	errors "golang.org/x/xerrors"
43)
44
45type contextKeyType int
46
47const (
48	instanceKey contextKeyType = iota
49	traceKey
50)
51
52// An Instance holds all debug information associated with a gopls instance.
53type Instance struct {
54	Logfile       string
55	StartTime     time.Time
56	ServerAddress string
57	Workdir       string
58	OCAgentConfig string
59
60	LogWriter io.Writer
61
62	exporter event.Exporter
63
64	ocagent    *ocagent.Exporter
65	prometheus *prometheus.Exporter
66	rpcs       *Rpcs
67	traces     *traces
68	State      *State
69
70	serveMu              sync.Mutex
71	debugAddress         string
72	listenedDebugAddress string
73}
74
75// State holds debugging information related to the server state.
76type State struct {
77	mu      sync.Mutex
78	clients []*Client
79	servers []*Server
80
81	// bugs maps bug description -> formatted event
82	bugs map[string]string
83}
84
85func Bug(ctx context.Context, desc, format string, args ...interface{}) {
86	labels := []label.Label{tag.Bug.Of(desc)}
87	_, file, line, ok := runtime.Caller(1)
88	if ok {
89		labels = append(labels, tag.Callsite.Of(fmt.Sprintf("%s:%d", file, line)))
90	}
91	msg := fmt.Sprintf(format, args...)
92	event.Log(ctx, msg, labels...)
93}
94
95type bug struct {
96	Description, Event string
97}
98
99func (st *State) Bugs() []bug {
100	st.mu.Lock()
101	defer st.mu.Unlock()
102	var bugs []bug
103	for k, v := range st.bugs {
104		bugs = append(bugs, bug{k, v})
105	}
106	sort.Slice(bugs, func(i, j int) bool {
107		return bugs[i].Description < bugs[j].Description
108	})
109	return bugs
110}
111
112func (st *State) recordBug(description, event string) {
113	st.mu.Lock()
114	defer st.mu.Unlock()
115	if st.bugs == nil {
116		st.bugs = make(map[string]string)
117	}
118	st.bugs[description] = event
119}
120
121// Caches returns the set of Cache objects currently being served.
122func (st *State) Caches() []*cache.Cache {
123	var caches []*cache.Cache
124	seen := make(map[string]struct{})
125	for _, client := range st.Clients() {
126		cache, ok := client.Session.Cache().(*cache.Cache)
127		if !ok {
128			continue
129		}
130		if _, found := seen[cache.ID()]; found {
131			continue
132		}
133		seen[cache.ID()] = struct{}{}
134		caches = append(caches, cache)
135	}
136	return caches
137}
138
139// Cache returns the Cache that matches the supplied id.
140func (st *State) Cache(id string) *cache.Cache {
141	for _, c := range st.Caches() {
142		if c.ID() == id {
143			return c
144		}
145	}
146	return nil
147}
148
149// Sessions returns the set of Session objects currently being served.
150func (st *State) Sessions() []*cache.Session {
151	var sessions []*cache.Session
152	for _, client := range st.Clients() {
153		sessions = append(sessions, client.Session)
154	}
155	return sessions
156}
157
158// Session returns the Session that matches the supplied id.
159func (st *State) Session(id string) *cache.Session {
160	for _, s := range st.Sessions() {
161		if s.ID() == id {
162			return s
163		}
164	}
165	return nil
166}
167
168// Views returns the set of View objects currently being served.
169func (st *State) Views() []*cache.View {
170	var views []*cache.View
171	for _, s := range st.Sessions() {
172		for _, v := range s.Views() {
173			if cv, ok := v.(*cache.View); ok {
174				views = append(views, cv)
175			}
176		}
177	}
178	return views
179}
180
181// View returns the View that matches the supplied id.
182func (st *State) View(id string) *cache.View {
183	for _, v := range st.Views() {
184		if v.ID() == id {
185			return v
186		}
187	}
188	return nil
189}
190
191// Clients returns the set of Clients currently being served.
192func (st *State) Clients() []*Client {
193	st.mu.Lock()
194	defer st.mu.Unlock()
195	clients := make([]*Client, len(st.clients))
196	copy(clients, st.clients)
197	return clients
198}
199
200// Client returns the Client matching the supplied id.
201func (st *State) Client(id string) *Client {
202	for _, c := range st.Clients() {
203		if c.Session.ID() == id {
204			return c
205		}
206	}
207	return nil
208}
209
210// Servers returns the set of Servers the instance is currently connected to.
211func (st *State) Servers() []*Server {
212	st.mu.Lock()
213	defer st.mu.Unlock()
214	servers := make([]*Server, len(st.servers))
215	copy(servers, st.servers)
216	return servers
217}
218
219// A Client is an incoming connection from a remote client.
220type Client struct {
221	Session      *cache.Session
222	DebugAddress string
223	Logfile      string
224	GoplsPath    string
225	ServerID     string
226	Service      protocol.Server
227}
228
229// A Server is an outgoing connection to a remote LSP server.
230type Server struct {
231	ID           string
232	DebugAddress string
233	Logfile      string
234	GoplsPath    string
235	ClientID     string
236}
237
238// AddClient adds a client to the set being served.
239func (st *State) addClient(session *cache.Session) {
240	st.mu.Lock()
241	defer st.mu.Unlock()
242	st.clients = append(st.clients, &Client{Session: session})
243}
244
245// DropClient removes a client from the set being served.
246func (st *State) dropClient(session source.Session) {
247	st.mu.Lock()
248	defer st.mu.Unlock()
249	for i, c := range st.clients {
250		if c.Session == session {
251			copy(st.clients[i:], st.clients[i+1:])
252			st.clients[len(st.clients)-1] = nil
253			st.clients = st.clients[:len(st.clients)-1]
254			return
255		}
256	}
257}
258
259// AddServer adds a server to the set being queried. In practice, there should
260// be at most one remote server.
261func (st *State) updateServer(server *Server) {
262	st.mu.Lock()
263	defer st.mu.Unlock()
264	for i, existing := range st.servers {
265		if existing.ID == server.ID {
266			// Replace, rather than mutate, to avoid a race.
267			newServers := make([]*Server, len(st.servers))
268			copy(newServers, st.servers[:i])
269			newServers[i] = server
270			copy(newServers[i+1:], st.servers[i+1:])
271			st.servers = newServers
272			return
273		}
274	}
275	st.servers = append(st.servers, server)
276}
277
278// DropServer drops a server from the set being queried.
279func (st *State) dropServer(id string) {
280	st.mu.Lock()
281	defer st.mu.Unlock()
282	for i, s := range st.servers {
283		if s.ID == id {
284			copy(st.servers[i:], st.servers[i+1:])
285			st.servers[len(st.servers)-1] = nil
286			st.servers = st.servers[:len(st.servers)-1]
287			return
288		}
289	}
290}
291
292// an http.ResponseWriter that filters writes
293type filterResponse struct {
294	w    http.ResponseWriter
295	edit func([]byte) []byte
296}
297
298func (c filterResponse) Header() http.Header {
299	return c.w.Header()
300}
301
302func (c filterResponse) Write(buf []byte) (int, error) {
303	ans := c.edit(buf)
304	return c.w.Write(ans)
305}
306
307func (c filterResponse) WriteHeader(n int) {
308	c.w.WriteHeader(n)
309}
310
311// replace annoying nuls by spaces
312func cmdline(w http.ResponseWriter, r *http.Request) {
313	fake := filterResponse{
314		w: w,
315		edit: func(buf []byte) []byte {
316			return bytes.ReplaceAll(buf, []byte{0}, []byte{' '})
317		},
318	}
319	pprof.Cmdline(fake, r)
320}
321
322func (i *Instance) getCache(r *http.Request) interface{} {
323	return i.State.Cache(path.Base(r.URL.Path))
324}
325
326func (i *Instance) getSession(r *http.Request) interface{} {
327	return i.State.Session(path.Base(r.URL.Path))
328}
329
330func (i *Instance) getClient(r *http.Request) interface{} {
331	return i.State.Client(path.Base(r.URL.Path))
332}
333
334func (i *Instance) getServer(r *http.Request) interface{} {
335	i.State.mu.Lock()
336	defer i.State.mu.Unlock()
337	id := path.Base(r.URL.Path)
338	for _, s := range i.State.servers {
339		if s.ID == id {
340			return s
341		}
342	}
343	return nil
344}
345
346func (i *Instance) getView(r *http.Request) interface{} {
347	return i.State.View(path.Base(r.URL.Path))
348}
349
350func (i *Instance) getFile(r *http.Request) interface{} {
351	identifier := path.Base(r.URL.Path)
352	sid := path.Base(path.Dir(r.URL.Path))
353	s := i.State.Session(sid)
354	if s == nil {
355		return nil
356	}
357	for _, o := range s.Overlays() {
358		if o.FileIdentity().Hash == identifier {
359			return o
360		}
361	}
362	return nil
363}
364
365func (i *Instance) getInfo(r *http.Request) interface{} {
366	buf := &bytes.Buffer{}
367	i.PrintServerInfo(r.Context(), buf)
368	return template.HTML(buf.String())
369}
370
371func (i *Instance) AddService(s protocol.Server, session *cache.Session) {
372	for _, c := range i.State.clients {
373		if c.Session == session {
374			c.Service = s
375			return
376		}
377	}
378	stdlog.Printf("unable to find a Client to add the protocol.Server to")
379}
380
381func getMemory(_ *http.Request) interface{} {
382	var m runtime.MemStats
383	runtime.ReadMemStats(&m)
384	return m
385}
386
387func init() {
388	event.SetExporter(makeGlobalExporter(os.Stderr))
389}
390
391func GetInstance(ctx context.Context) *Instance {
392	if ctx == nil {
393		return nil
394	}
395	v := ctx.Value(instanceKey)
396	if v == nil {
397		return nil
398	}
399	return v.(*Instance)
400}
401
402// WithInstance creates debug instance ready for use using the supplied
403// configuration and stores it in the returned context.
404func WithInstance(ctx context.Context, workdir, agent string) context.Context {
405	i := &Instance{
406		StartTime:     time.Now(),
407		Workdir:       workdir,
408		OCAgentConfig: agent,
409	}
410	i.LogWriter = os.Stderr
411	ocConfig := ocagent.Discover()
412	//TODO: we should not need to adjust the discovered configuration
413	ocConfig.Address = i.OCAgentConfig
414	i.ocagent = ocagent.Connect(ocConfig)
415	i.prometheus = prometheus.New()
416	i.rpcs = &Rpcs{}
417	i.traces = &traces{}
418	i.State = &State{}
419	i.exporter = makeInstanceExporter(i)
420	return context.WithValue(ctx, instanceKey, i)
421}
422
423// SetLogFile sets the logfile for use with this instance.
424func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) {
425	// TODO: probably a better solution for deferring closure to the caller would
426	// be for the debug instance to itself be closed, but this fixes the
427	// immediate bug of logs not being captured.
428	closeLog := func() {}
429	if logfile != "" {
430		if logfile == "auto" {
431			if isDaemon {
432				logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-daemon-%d.log", os.Getpid()))
433			} else {
434				logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
435			}
436		}
437		f, err := os.Create(logfile)
438		if err != nil {
439			return nil, errors.Errorf("unable to create log file: %w", err)
440		}
441		closeLog = func() {
442			defer f.Close()
443		}
444		stdlog.SetOutput(io.MultiWriter(os.Stderr, f))
445		i.LogWriter = f
446	}
447	i.Logfile = logfile
448	return closeLog, nil
449}
450
451// Serve starts and runs a debug server in the background on the given addr.
452// It also logs the port the server starts on, to allow for :0 auto assigned
453// ports.
454func (i *Instance) Serve(ctx context.Context, addr string) (string, error) {
455	stdlog.SetFlags(stdlog.Lshortfile)
456	if addr == "" {
457		return "", nil
458	}
459	i.serveMu.Lock()
460	defer i.serveMu.Unlock()
461
462	if i.listenedDebugAddress != "" {
463		// Already serving. Return the bound address.
464		return i.listenedDebugAddress, nil
465	}
466
467	i.debugAddress = addr
468	listener, err := net.Listen("tcp", i.debugAddress)
469	if err != nil {
470		return "", err
471	}
472	i.listenedDebugAddress = listener.Addr().String()
473
474	port := listener.Addr().(*net.TCPAddr).Port
475	if strings.HasSuffix(i.debugAddress, ":0") {
476		stdlog.Printf("debug server listening at http://localhost:%d", port)
477	}
478	event.Log(ctx, "Debug serving", tag.Port.Of(port))
479	go func() {
480		mux := http.NewServeMux()
481		mux.HandleFunc("/", render(MainTmpl, func(*http.Request) interface{} { return i }))
482		mux.HandleFunc("/debug/", render(DebugTmpl, nil))
483		mux.HandleFunc("/debug/pprof/", pprof.Index)
484		mux.HandleFunc("/debug/pprof/cmdline", cmdline)
485		mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
486		mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
487		mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
488		if i.prometheus != nil {
489			mux.HandleFunc("/metrics/", i.prometheus.Serve)
490		}
491		if i.rpcs != nil {
492			mux.HandleFunc("/rpc/", render(RPCTmpl, i.rpcs.getData))
493		}
494		if i.traces != nil {
495			mux.HandleFunc("/trace/", render(TraceTmpl, i.traces.getData))
496		}
497		mux.HandleFunc("/cache/", render(CacheTmpl, i.getCache))
498		mux.HandleFunc("/session/", render(SessionTmpl, i.getSession))
499		mux.HandleFunc("/view/", render(ViewTmpl, i.getView))
500		mux.HandleFunc("/client/", render(ClientTmpl, i.getClient))
501		mux.HandleFunc("/server/", render(ServerTmpl, i.getServer))
502		mux.HandleFunc("/file/", render(FileTmpl, i.getFile))
503		mux.HandleFunc("/info", render(InfoTmpl, i.getInfo))
504		mux.HandleFunc("/memory", render(MemoryTmpl, getMemory))
505		if err := http.Serve(listener, mux); err != nil {
506			event.Error(ctx, "Debug server failed", err)
507			return
508		}
509		event.Log(ctx, "Debug server finished")
510	}()
511	return i.listenedDebugAddress, nil
512}
513
514func (i *Instance) DebugAddress() string {
515	i.serveMu.Lock()
516	defer i.serveMu.Unlock()
517	return i.debugAddress
518}
519
520func (i *Instance) ListenedDebugAddress() string {
521	i.serveMu.Lock()
522	defer i.serveMu.Unlock()
523	return i.listenedDebugAddress
524}
525
526// MonitorMemory starts recording memory statistics each second.
527func (i *Instance) MonitorMemory(ctx context.Context) {
528	tick := time.NewTicker(time.Second)
529	nextThresholdGiB := uint64(1)
530	go func() {
531		for {
532			<-tick.C
533			var mem runtime.MemStats
534			runtime.ReadMemStats(&mem)
535			if mem.HeapAlloc < nextThresholdGiB*1<<30 {
536				continue
537			}
538			if err := i.writeMemoryDebug(nextThresholdGiB, true); err != nil {
539				event.Error(ctx, "writing memory debug info", err)
540			}
541			if err := i.writeMemoryDebug(nextThresholdGiB, false); err != nil {
542				event.Error(ctx, "writing memory debug info", err)
543			}
544			event.Log(ctx, fmt.Sprintf("Wrote memory usage debug info to %v", os.TempDir()))
545			nextThresholdGiB++
546		}
547	}()
548}
549
550func (i *Instance) writeMemoryDebug(threshold uint64, withNames bool) error {
551	suffix := "withnames"
552	if !withNames {
553		suffix = "nonames"
554	}
555
556	filename := fmt.Sprintf("gopls.%d-%dGiB-%s.zip", os.Getpid(), threshold, suffix)
557	zipf, err := os.OpenFile(filepath.Join(os.TempDir(), filename), os.O_CREATE|os.O_RDWR, 0644)
558	if err != nil {
559		return err
560	}
561	zipw := zip.NewWriter(zipf)
562
563	f, err := zipw.Create("heap.pb.gz")
564	if err != nil {
565		return err
566	}
567	if err := rpprof.Lookup("heap").WriteTo(f, 0); err != nil {
568		return err
569	}
570
571	f, err = zipw.Create("goroutines.txt")
572	if err != nil {
573		return err
574	}
575	if err := rpprof.Lookup("goroutine").WriteTo(f, 1); err != nil {
576		return err
577	}
578
579	for _, cache := range i.State.Caches() {
580		cf, err := zipw.Create(fmt.Sprintf("cache-%v.html", cache.ID()))
581		if err != nil {
582			return err
583		}
584		if _, err := cf.Write([]byte(cache.PackageStats(withNames))); err != nil {
585			return err
586		}
587	}
588
589	if err := zipw.Close(); err != nil {
590		return err
591	}
592	return zipf.Close()
593}
594
595func makeGlobalExporter(stderr io.Writer) event.Exporter {
596	p := export.Printer{}
597	var pMu sync.Mutex
598	return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
599		i := GetInstance(ctx)
600
601		if event.IsLog(ev) {
602			// Don't log context cancellation errors.
603			if err := keys.Err.Get(ev); errors.Is(err, context.Canceled) {
604				return ctx
605			}
606			// Make sure any log messages without an instance go to stderr.
607			if i == nil {
608				pMu.Lock()
609				p.WriteEvent(stderr, ev, lm)
610				pMu.Unlock()
611			}
612			level := log.LabeledLevel(lm)
613			// Exclude trace logs from LSP logs.
614			if level < log.Trace {
615				ctx = protocol.LogEvent(ctx, ev, lm, messageType(level))
616			}
617		}
618		if i == nil {
619			return ctx
620		}
621		return i.exporter(ctx, ev, lm)
622	}
623}
624
625func messageType(l log.Level) protocol.MessageType {
626	switch l {
627	case log.Error:
628		return protocol.Error
629	case log.Warning:
630		return protocol.Warning
631	case log.Debug:
632		return protocol.Log
633	}
634	return protocol.Info
635}
636
637func makeInstanceExporter(i *Instance) event.Exporter {
638	exporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
639		if i.ocagent != nil {
640			ctx = i.ocagent.ProcessEvent(ctx, ev, lm)
641		}
642		if i.prometheus != nil {
643			ctx = i.prometheus.ProcessEvent(ctx, ev, lm)
644		}
645		if i.rpcs != nil {
646			ctx = i.rpcs.ProcessEvent(ctx, ev, lm)
647		}
648		if i.traces != nil {
649			ctx = i.traces.ProcessEvent(ctx, ev, lm)
650		}
651		if event.IsLog(ev) {
652			if s := cache.KeyCreateSession.Get(ev); s != nil {
653				i.State.addClient(s)
654			}
655			if sid := tag.NewServer.Get(ev); sid != "" {
656				i.State.updateServer(&Server{
657					ID:           sid,
658					Logfile:      tag.Logfile.Get(ev),
659					DebugAddress: tag.DebugAddress.Get(ev),
660					GoplsPath:    tag.GoplsPath.Get(ev),
661					ClientID:     tag.ClientID.Get(ev),
662				})
663			}
664			if s := cache.KeyShutdownSession.Get(ev); s != nil {
665				i.State.dropClient(s)
666			}
667			if sid := tag.EndServer.Get(ev); sid != "" {
668				i.State.dropServer(sid)
669			}
670			if s := cache.KeyUpdateSession.Get(ev); s != nil {
671				if c := i.State.Client(s.ID()); c != nil {
672					c.DebugAddress = tag.DebugAddress.Get(ev)
673					c.Logfile = tag.Logfile.Get(ev)
674					c.ServerID = tag.ServerID.Get(ev)
675					c.GoplsPath = tag.GoplsPath.Get(ev)
676				}
677			}
678		}
679		if b := tag.Bug.Get(ev); b != "" {
680			i.State.recordBug(b, fmt.Sprintf("%v", ev))
681		}
682		return ctx
683	}
684	// StdTrace must be above export.Spans below (by convention, export
685	// middleware applies its wrapped exporter last).
686	exporter = StdTrace(exporter)
687	metrics := metric.Config{}
688	registerMetrics(&metrics)
689	exporter = metrics.Exporter(exporter)
690	exporter = export.Spans(exporter)
691	exporter = export.Labels(exporter)
692	return exporter
693}
694
695type dataFunc func(*http.Request) interface{}
696
697func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) {
698	return func(w http.ResponseWriter, r *http.Request) {
699		var data interface{}
700		if fun != nil {
701			data = fun(r)
702		}
703		if err := tmpl.Execute(w, data); err != nil {
704			event.Error(context.Background(), "", err)
705			http.Error(w, err.Error(), http.StatusInternalServerError)
706		}
707	}
708}
709
710func commas(s string) string {
711	for i := len(s); i > 3; {
712		i -= 3
713		s = s[:i] + "," + s[i:]
714	}
715	return s
716}
717
718func fuint64(v uint64) string {
719	return commas(strconv.FormatUint(v, 10))
720}
721
722func fuint32(v uint32) string {
723	return commas(strconv.FormatUint(uint64(v), 10))
724}
725
726func fcontent(v []byte) string {
727	return string(v)
728}
729
730var BaseTemplate = template.Must(template.New("").Parse(`
731<html>
732<head>
733<title>{{template "title" .}}</title>
734<style>
735.profile-name{
736	display:inline-block;
737	width:6rem;
738}
739td.value {
740  text-align: right;
741}
742ul.events {
743	list-style-type: none;
744}
745
746</style>
747{{block "head" .}}{{end}}
748</head>
749<body>
750<a href="/">Main</a>
751<a href="/info">Info</a>
752<a href="/memory">Memory</a>
753<a href="/metrics">Metrics</a>
754<a href="/rpc">RPC</a>
755<a href="/trace">Trace</a>
756<hr>
757<h1>{{template "title" .}}</h1>
758{{block "body" .}}
759Unknown page
760{{end}}
761</body>
762</html>
763
764{{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
765{{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}}
766{{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
767{{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
768{{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
769{{define "filelink"}}<a href="/file/{{.Session}}/{{.FileIdentity.Hash}}">{{.FileIdentity.URI}}</a>{{end}}
770`)).Funcs(template.FuncMap{
771	"fuint64":  fuint64,
772	"fuint32":  fuint32,
773	"fcontent": fcontent,
774	"localAddress": func(s string) string {
775		// Try to translate loopback addresses to localhost, both for cosmetics and
776		// because unspecified ipv6 addresses can break links on Windows.
777		//
778		// TODO(rfindley): In the future, it would be better not to assume the
779		// server is running on localhost, and instead construct this address using
780		// the remote host.
781		host, port, err := net.SplitHostPort(s)
782		if err != nil {
783			return s
784		}
785		ip := net.ParseIP(host)
786		if ip == nil {
787			return s
788		}
789		if ip.IsLoopback() || ip.IsUnspecified() {
790			return "localhost:" + port
791		}
792		return s
793	},
794	"options": func(s *cache.Session) []string {
795		return showOptions(s.Options())
796	},
797})
798
799var MainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
800{{define "title"}}GoPls server information{{end}}
801{{define "body"}}
802<h2>Caches</h2>
803<ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
804<h2>Sessions</h2>
805<ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
806<h2>Views</h2>
807<ul>{{range .State.Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
808<h2>Clients</h2>
809<ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul>
810<h2>Servers</h2>
811<ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
812<h2>Known bugs encountered</h2>
813<dl>{{range .State.Bugs}}<dt>{{.Description}}</dt><dd>{{.Event}}</dd>{{end}}</dl>
814{{end}}
815`))
816
817var InfoTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
818{{define "title"}}GoPls version information{{end}}
819{{define "body"}}
820{{.}}
821{{end}}
822`))
823
824var MemoryTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
825{{define "title"}}GoPls memory usage{{end}}
826{{define "head"}}<meta http-equiv="refresh" content="5">{{end}}
827{{define "body"}}
828<h2>Stats</h2>
829<table>
830<tr><td class="label">Allocated bytes</td><td class="value">{{fuint64 .HeapAlloc}}</td></tr>
831<tr><td class="label">Total allocated bytes</td><td class="value">{{fuint64 .TotalAlloc}}</td></tr>
832<tr><td class="label">System bytes</td><td class="value">{{fuint64 .Sys}}</td></tr>
833<tr><td class="label">Heap system bytes</td><td class="value">{{fuint64 .HeapSys}}</td></tr>
834<tr><td class="label">Malloc calls</td><td class="value">{{fuint64 .Mallocs}}</td></tr>
835<tr><td class="label">Frees</td><td class="value">{{fuint64 .Frees}}</td></tr>
836<tr><td class="label">Idle heap bytes</td><td class="value">{{fuint64 .HeapIdle}}</td></tr>
837<tr><td class="label">In use bytes</td><td class="value">{{fuint64 .HeapInuse}}</td></tr>
838<tr><td class="label">Released to system bytes</td><td class="value">{{fuint64 .HeapReleased}}</td></tr>
839<tr><td class="label">Heap object count</td><td class="value">{{fuint64 .HeapObjects}}</td></tr>
840<tr><td class="label">Stack in use bytes</td><td class="value">{{fuint64 .StackInuse}}</td></tr>
841<tr><td class="label">Stack from system bytes</td><td class="value">{{fuint64 .StackSys}}</td></tr>
842<tr><td class="label">Bucket hash bytes</td><td class="value">{{fuint64 .BuckHashSys}}</td></tr>
843<tr><td class="label">GC metadata bytes</td><td class="value">{{fuint64 .GCSys}}</td></tr>
844<tr><td class="label">Off heap bytes</td><td class="value">{{fuint64 .OtherSys}}</td></tr>
845</table>
846<h2>By size</h2>
847<table>
848<tr><th>Size</th><th>Mallocs</th><th>Frees</th></tr>
849{{range .BySize}}<tr><td class="value">{{fuint32 .Size}}</td><td class="value">{{fuint64 .Mallocs}}</td><td class="value">{{fuint64 .Frees}}</td></tr>{{end}}
850</table>
851{{end}}
852`))
853
854var DebugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
855{{define "title"}}GoPls Debug pages{{end}}
856{{define "body"}}
857<a href="/debug/pprof">Profiling</a>
858{{end}}
859`))
860
861var CacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
862{{define "title"}}Cache {{.ID}}{{end}}
863{{define "body"}}
864<h2>memoize.Store entries</h2>
865<ul>{{range $k,$v := .MemStats}}<li>{{$k}} - {{$v}}</li>{{end}}</ul>
866<h2>Per-package usage - not accurate, for guidance only</h2>
867{{.PackageStats true}}
868{{end}}
869`))
870
871var ClientTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
872{{define "title"}}Client {{.Session.ID}}{{end}}
873{{define "body"}}
874Using session: <b>{{template "sessionlink" .Session.ID}}</b><br>
875{{if .DebugAddress}}Debug this client at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
876Logfile: {{.Logfile}}<br>
877Gopls Path: {{.GoplsPath}}<br>
878<h2>Diagnostics</h2>
879{{/*Service: []protocol.Server; each server has map[uri]fileReports;
880	each fileReport: map[diagnosticSoure]diagnosticReport
881	diagnosticSource is one of 5 source
882	diagnosticReport: snapshotID and map[hash]*source.Diagnostic
883	sourceDiagnostic: struct {
884		Range    protocol.Range
885		Message  string
886		Source   string
887		Code     string
888		CodeHref string
889		Severity protocol.DiagnosticSeverity
890		Tags     []protocol.DiagnosticTag
891
892		Related []RelatedInformation
893	}
894	RelatedInformation: struct {
895		URI     span.URI
896		Range   protocol.Range
897		Message string
898	}
899	*/}}
900<ul>{{range $k, $v := .Service.Diagnostics}}<li>{{$k}}:<ol>{{range $v}}<li>{{.}}</li>{{end}}</ol></li>{{end}}</ul>
901{{end}}
902`))
903
904var ServerTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
905{{define "title"}}Server {{.ID}}{{end}}
906{{define "body"}}
907{{if .DebugAddress}}Debug this server at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
908Logfile: {{.Logfile}}<br>
909Gopls Path: {{.GoplsPath}}<br>
910{{end}}
911`))
912
913var SessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
914{{define "title"}}Session {{.ID}}{{end}}
915{{define "body"}}
916From: <b>{{template "cachelink" .Cache.ID}}</b><br>
917<h2>Views</h2>
918<ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} in {{.Folder}}</li>{{end}}</ul>
919<h2>Overlays</h2>
920<ul>{{range .Overlays}}<li>{{template "filelink" .}}</li>{{end}}</ul>
921<h2>Options</h2>
922{{range options .}}<p>{{.}}{{end}}
923{{end}}
924`))
925
926var ViewTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
927{{define "title"}}View {{.ID}}{{end}}
928{{define "body"}}
929Name: <b>{{.Name}}</b><br>
930Folder: <b>{{.Folder}}</b><br>
931From: <b>{{template "sessionlink" .Session.ID}}</b><br>
932<h2>Environment</h2>
933<ul>{{range .Options.Env}}<li>{{.}}</li>{{end}}</ul>
934{{end}}
935`))
936
937var FileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
938{{define "title"}}Overlay {{.FileIdentity.Hash}}{{end}}
939{{define "body"}}
940{{with .}}
941	From: <b>{{template "sessionlink" .Session}}</b><br>
942	URI: <b>{{.URI}}</b><br>
943	Identifier: <b>{{.FileIdentity.Hash}}</b><br>
944	Version: <b>{{.Version}}</b><br>
945	Kind: <b>{{.Kind}}</b><br>
946{{end}}
947<h3>Contents</h3>
948<pre>{{fcontent .Read}}</pre>
949{{end}}
950`))
951