golang.org/x/tools/gopls@v0.15.3/internal/debug/serve.go (about)

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