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