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