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