aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/debug/serve.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/debug/serve.go')
-rw-r--r--internal/lsp/debug/serve.go954
1 files changed, 0 insertions, 954 deletions
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
deleted file mode 100644
index b6dba60ab..000000000
--- a/internal/lsp/debug/serve.go
+++ /dev/null
@@ -1,954 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package debug
-
-import (
- "archive/zip"
- "bytes"
- "context"
- "fmt"
- "html/template"
- "io"
- stdlog "log"
- "net"
- "net/http"
- "net/http/pprof"
- "os"
- "path"
- "path/filepath"
- "runtime"
- rpprof "runtime/pprof"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/event/core"
- "golang.org/x/tools/internal/event/export"
- "golang.org/x/tools/internal/event/export/metric"
- "golang.org/x/tools/internal/event/export/ocagent"
- "golang.org/x/tools/internal/event/export/prometheus"
- "golang.org/x/tools/internal/event/keys"
- "golang.org/x/tools/internal/event/label"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/debug/log"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- errors "golang.org/x/xerrors"
-)
-
-type contextKeyType int
-
-const (
- instanceKey contextKeyType = iota
- traceKey
-)
-
-// An Instance holds all debug information associated with a gopls instance.
-type Instance struct {
- Logfile string
- StartTime time.Time
- ServerAddress string
- Workdir string
- OCAgentConfig string
-
- LogWriter io.Writer
-
- exporter event.Exporter
-
- ocagent *ocagent.Exporter
- prometheus *prometheus.Exporter
- rpcs *Rpcs
- traces *traces
- State *State
-
- serveMu sync.Mutex
- debugAddress string
- listenedDebugAddress string
-}
-
-// State holds debugging information related to the server state.
-type State struct {
- mu sync.Mutex
- clients []*Client
- servers []*Server
-
- // bugs maps bug description -> formatted event
- bugs map[string]string
-}
-
-func Bug(ctx context.Context, desc, format string, args ...interface{}) {
- labels := []label.Label{tag.Bug.Of(desc)}
- _, file, line, ok := runtime.Caller(1)
- if ok {
- labels = append(labels, tag.Callsite.Of(fmt.Sprintf("%s:%d", file, line)))
- }
- msg := fmt.Sprintf(format, args...)
- event.Log(ctx, msg, labels...)
-}
-
-type bug struct {
- Description, Event string
-}
-
-func (st *State) Bugs() []bug {
- st.mu.Lock()
- defer st.mu.Unlock()
- var bugs []bug
- for k, v := range st.bugs {
- bugs = append(bugs, bug{k, v})
- }
- sort.Slice(bugs, func(i, j int) bool {
- return bugs[i].Description < bugs[j].Description
- })
- return bugs
-}
-
-func (st *State) recordBug(description, event string) {
- st.mu.Lock()
- defer st.mu.Unlock()
- if st.bugs == nil {
- st.bugs = make(map[string]string)
- }
- st.bugs[description] = event
-}
-
-// Caches returns the set of Cache objects currently being served.
-func (st *State) Caches() []*cache.Cache {
- var caches []*cache.Cache
- seen := make(map[string]struct{})
- for _, client := range st.Clients() {
- cache, ok := client.Session.Cache().(*cache.Cache)
- if !ok {
- continue
- }
- if _, found := seen[cache.ID()]; found {
- continue
- }
- seen[cache.ID()] = struct{}{}
- caches = append(caches, cache)
- }
- return caches
-}
-
-// Cache returns the Cache that matches the supplied id.
-func (st *State) Cache(id string) *cache.Cache {
- for _, c := range st.Caches() {
- if c.ID() == id {
- return c
- }
- }
- return nil
-}
-
-// Sessions returns the set of Session objects currently being served.
-func (st *State) Sessions() []*cache.Session {
- var sessions []*cache.Session
- for _, client := range st.Clients() {
- sessions = append(sessions, client.Session)
- }
- return sessions
-}
-
-// Session returns the Session that matches the supplied id.
-func (st *State) Session(id string) *cache.Session {
- for _, s := range st.Sessions() {
- if s.ID() == id {
- return s
- }
- }
- return nil
-}
-
-// Views returns the set of View objects currently being served.
-func (st *State) Views() []*cache.View {
- var views []*cache.View
- for _, s := range st.Sessions() {
- for _, v := range s.Views() {
- if cv, ok := v.(*cache.View); ok {
- views = append(views, cv)
- }
- }
- }
- return views
-}
-
-// View returns the View that matches the supplied id.
-func (st *State) View(id string) *cache.View {
- for _, v := range st.Views() {
- if v.ID() == id {
- return v
- }
- }
- return nil
-}
-
-// Clients returns the set of Clients currently being served.
-func (st *State) Clients() []*Client {
- st.mu.Lock()
- defer st.mu.Unlock()
- clients := make([]*Client, len(st.clients))
- copy(clients, st.clients)
- return clients
-}
-
-// Client returns the Client matching the supplied id.
-func (st *State) Client(id string) *Client {
- for _, c := range st.Clients() {
- if c.Session.ID() == id {
- return c
- }
- }
- return nil
-}
-
-// Servers returns the set of Servers the instance is currently connected to.
-func (st *State) Servers() []*Server {
- st.mu.Lock()
- defer st.mu.Unlock()
- servers := make([]*Server, len(st.servers))
- copy(servers, st.servers)
- return servers
-}
-
-// A Client is an incoming connection from a remote client.
-type Client struct {
- Session *cache.Session
- DebugAddress string
- Logfile string
- GoplsPath string
- ServerID string
- Service protocol.Server
-}
-
-// A Server is an outgoing connection to a remote LSP server.
-type Server struct {
- ID string
- DebugAddress string
- Logfile string
- GoplsPath string
- ClientID string
-}
-
-// AddClient adds a client to the set being served.
-func (st *State) addClient(session *cache.Session) {
- st.mu.Lock()
- defer st.mu.Unlock()
- st.clients = append(st.clients, &Client{Session: session})
-}
-
-// DropClient removes a client from the set being served.
-func (st *State) dropClient(session source.Session) {
- st.mu.Lock()
- defer st.mu.Unlock()
- for i, c := range st.clients {
- if c.Session == session {
- copy(st.clients[i:], st.clients[i+1:])
- st.clients[len(st.clients)-1] = nil
- st.clients = st.clients[:len(st.clients)-1]
- return
- }
- }
-}
-
-// AddServer adds a server to the set being queried. In practice, there should
-// be at most one remote server.
-func (st *State) updateServer(server *Server) {
- st.mu.Lock()
- defer st.mu.Unlock()
- for i, existing := range st.servers {
- if existing.ID == server.ID {
- // Replace, rather than mutate, to avoid a race.
- newServers := make([]*Server, len(st.servers))
- copy(newServers, st.servers[:i])
- newServers[i] = server
- copy(newServers[i+1:], st.servers[i+1:])
- st.servers = newServers
- return
- }
- }
- st.servers = append(st.servers, server)
-}
-
-// DropServer drops a server from the set being queried.
-func (st *State) dropServer(id string) {
- st.mu.Lock()
- defer st.mu.Unlock()
- for i, s := range st.servers {
- if s.ID == id {
- copy(st.servers[i:], st.servers[i+1:])
- st.servers[len(st.servers)-1] = nil
- st.servers = st.servers[:len(st.servers)-1]
- return
- }
- }
-}
-
-// an http.ResponseWriter that filters writes
-type filterResponse struct {
- w http.ResponseWriter
- edit func([]byte) []byte
-}
-
-func (c filterResponse) Header() http.Header {
- return c.w.Header()
-}
-
-func (c filterResponse) Write(buf []byte) (int, error) {
- ans := c.edit(buf)
- return c.w.Write(ans)
-}
-
-func (c filterResponse) WriteHeader(n int) {
- c.w.WriteHeader(n)
-}
-
-// replace annoying nuls by spaces
-func cmdline(w http.ResponseWriter, r *http.Request) {
- fake := filterResponse{
- w: w,
- edit: func(buf []byte) []byte {
- return bytes.ReplaceAll(buf, []byte{0}, []byte{' '})
- },
- }
- pprof.Cmdline(fake, r)
-}
-
-func (i *Instance) getCache(r *http.Request) interface{} {
- return i.State.Cache(path.Base(r.URL.Path))
-}
-
-func (i *Instance) getSession(r *http.Request) interface{} {
- return i.State.Session(path.Base(r.URL.Path))
-}
-
-func (i *Instance) getClient(r *http.Request) interface{} {
- return i.State.Client(path.Base(r.URL.Path))
-}
-
-func (i *Instance) getServer(r *http.Request) interface{} {
- i.State.mu.Lock()
- defer i.State.mu.Unlock()
- id := path.Base(r.URL.Path)
- for _, s := range i.State.servers {
- if s.ID == id {
- return s
- }
- }
- return nil
-}
-
-func (i *Instance) getView(r *http.Request) interface{} {
- return i.State.View(path.Base(r.URL.Path))
-}
-
-func (i *Instance) getFile(r *http.Request) interface{} {
- identifier := path.Base(r.URL.Path)
- sid := path.Base(path.Dir(r.URL.Path))
- s := i.State.Session(sid)
- if s == nil {
- return nil
- }
- for _, o := range s.Overlays() {
- if o.FileIdentity().Hash == identifier {
- return o
- }
- }
- return nil
-}
-
-func (i *Instance) getInfo(r *http.Request) interface{} {
- buf := &bytes.Buffer{}
- i.PrintServerInfo(r.Context(), buf)
- return template.HTML(buf.String())
-}
-
-func (i *Instance) AddService(s protocol.Server, session *cache.Session) {
- for _, c := range i.State.clients {
- if c.Session == session {
- c.Service = s
- return
- }
- }
- stdlog.Printf("unable to find a Client to add the protocol.Server to")
-}
-
-func getMemory(_ *http.Request) interface{} {
- var m runtime.MemStats
- runtime.ReadMemStats(&m)
- return m
-}
-
-func init() {
- event.SetExporter(makeGlobalExporter(os.Stderr))
-}
-
-func GetInstance(ctx context.Context) *Instance {
- if ctx == nil {
- return nil
- }
- v := ctx.Value(instanceKey)
- if v == nil {
- return nil
- }
- return v.(*Instance)
-}
-
-// WithInstance creates debug instance ready for use using the supplied
-// configuration and stores it in the returned context.
-func WithInstance(ctx context.Context, workdir, agent string) context.Context {
- i := &Instance{
- StartTime: time.Now(),
- Workdir: workdir,
- OCAgentConfig: agent,
- }
- i.LogWriter = os.Stderr
- ocConfig := ocagent.Discover()
- //TODO: we should not need to adjust the discovered configuration
- ocConfig.Address = i.OCAgentConfig
- i.ocagent = ocagent.Connect(ocConfig)
- i.prometheus = prometheus.New()
- i.rpcs = &Rpcs{}
- i.traces = &traces{}
- i.State = &State{}
- i.exporter = makeInstanceExporter(i)
- return context.WithValue(ctx, instanceKey, i)
-}
-
-// SetLogFile sets the logfile for use with this instance.
-func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) {
- // TODO: probably a better solution for deferring closure to the caller would
- // be for the debug instance to itself be closed, but this fixes the
- // immediate bug of logs not being captured.
- closeLog := func() {}
- if logfile != "" {
- if logfile == "auto" {
- if isDaemon {
- logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-daemon-%d.log", os.Getpid()))
- } else {
- logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
- }
- }
- f, err := os.Create(logfile)
- if err != nil {
- return nil, errors.Errorf("unable to create log file: %w", err)
- }
- closeLog = func() {
- defer f.Close()
- }
- stdlog.SetOutput(io.MultiWriter(os.Stderr, f))
- i.LogWriter = f
- }
- i.Logfile = logfile
- return closeLog, nil
-}
-
-// Serve starts and runs a debug server in the background on the given addr.
-// It also logs the port the server starts on, to allow for :0 auto assigned
-// ports.
-func (i *Instance) Serve(ctx context.Context, addr string) (string, error) {
- stdlog.SetFlags(stdlog.Lshortfile)
- if addr == "" {
- return "", nil
- }
- i.serveMu.Lock()
- defer i.serveMu.Unlock()
-
- if i.listenedDebugAddress != "" {
- // Already serving. Return the bound address.
- return i.listenedDebugAddress, nil
- }
-
- i.debugAddress = addr
- listener, err := net.Listen("tcp", i.debugAddress)
- if err != nil {
- return "", err
- }
- i.listenedDebugAddress = listener.Addr().String()
-
- port := listener.Addr().(*net.TCPAddr).Port
- if strings.HasSuffix(i.debugAddress, ":0") {
- stdlog.Printf("debug server listening at http://localhost:%d", port)
- }
- event.Log(ctx, "Debug serving", tag.Port.Of(port))
- go func() {
- mux := http.NewServeMux()
- mux.HandleFunc("/", render(MainTmpl, func(*http.Request) interface{} { return i }))
- mux.HandleFunc("/debug/", render(DebugTmpl, nil))
- mux.HandleFunc("/debug/pprof/", pprof.Index)
- mux.HandleFunc("/debug/pprof/cmdline", cmdline)
- mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
- mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
- mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
- if i.prometheus != nil {
- mux.HandleFunc("/metrics/", i.prometheus.Serve)
- }
- if i.rpcs != nil {
- mux.HandleFunc("/rpc/", render(RPCTmpl, i.rpcs.getData))
- }
- if i.traces != nil {
- mux.HandleFunc("/trace/", render(TraceTmpl, i.traces.getData))
- }
- mux.HandleFunc("/cache/", render(CacheTmpl, i.getCache))
- mux.HandleFunc("/session/", render(SessionTmpl, i.getSession))
- mux.HandleFunc("/view/", render(ViewTmpl, i.getView))
- mux.HandleFunc("/client/", render(ClientTmpl, i.getClient))
- mux.HandleFunc("/server/", render(ServerTmpl, i.getServer))
- mux.HandleFunc("/file/", render(FileTmpl, i.getFile))
- mux.HandleFunc("/info", render(InfoTmpl, i.getInfo))
- mux.HandleFunc("/memory", render(MemoryTmpl, getMemory))
- if err := http.Serve(listener, mux); err != nil {
- event.Error(ctx, "Debug server failed", err)
- return
- }
- event.Log(ctx, "Debug server finished")
- }()
- return i.listenedDebugAddress, nil
-}
-
-func (i *Instance) DebugAddress() string {
- i.serveMu.Lock()
- defer i.serveMu.Unlock()
- return i.debugAddress
-}
-
-func (i *Instance) ListenedDebugAddress() string {
- i.serveMu.Lock()
- defer i.serveMu.Unlock()
- return i.listenedDebugAddress
-}
-
-// MonitorMemory starts recording memory statistics each second.
-func (i *Instance) MonitorMemory(ctx context.Context) {
- tick := time.NewTicker(time.Second)
- nextThresholdGiB := uint64(1)
- go func() {
- for {
- <-tick.C
- var mem runtime.MemStats
- runtime.ReadMemStats(&mem)
- if mem.HeapAlloc < nextThresholdGiB*1<<30 {
- continue
- }
- if err := i.writeMemoryDebug(nextThresholdGiB, true); err != nil {
- event.Error(ctx, "writing memory debug info", err)
- }
- if err := i.writeMemoryDebug(nextThresholdGiB, false); err != nil {
- event.Error(ctx, "writing memory debug info", err)
- }
- event.Log(ctx, fmt.Sprintf("Wrote memory usage debug info to %v", os.TempDir()))
- nextThresholdGiB++
- }
- }()
-}
-
-func (i *Instance) writeMemoryDebug(threshold uint64, withNames bool) error {
- suffix := "withnames"
- if !withNames {
- suffix = "nonames"
- }
-
- filename := fmt.Sprintf("gopls.%d-%dGiB-%s.zip", os.Getpid(), threshold, suffix)
- zipf, err := os.OpenFile(filepath.Join(os.TempDir(), filename), os.O_CREATE|os.O_RDWR, 0644)
- if err != nil {
- return err
- }
- zipw := zip.NewWriter(zipf)
-
- f, err := zipw.Create("heap.pb.gz")
- if err != nil {
- return err
- }
- if err := rpprof.Lookup("heap").WriteTo(f, 0); err != nil {
- return err
- }
-
- f, err = zipw.Create("goroutines.txt")
- if err != nil {
- return err
- }
- if err := rpprof.Lookup("goroutine").WriteTo(f, 1); err != nil {
- return err
- }
-
- for _, cache := range i.State.Caches() {
- cf, err := zipw.Create(fmt.Sprintf("cache-%v.html", cache.ID()))
- if err != nil {
- return err
- }
- if _, err := cf.Write([]byte(cache.PackageStats(withNames))); err != nil {
- return err
- }
- }
-
- if err := zipw.Close(); err != nil {
- return err
- }
- return zipf.Close()
-}
-
-func makeGlobalExporter(stderr io.Writer) event.Exporter {
- p := export.Printer{}
- var pMu sync.Mutex
- return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
- i := GetInstance(ctx)
-
- if event.IsLog(ev) {
- // Don't log context cancellation errors.
- if err := keys.Err.Get(ev); errors.Is(err, context.Canceled) {
- return ctx
- }
- // Make sure any log messages without an instance go to stderr.
- if i == nil {
- pMu.Lock()
- p.WriteEvent(stderr, ev, lm)
- pMu.Unlock()
- }
- level := log.LabeledLevel(lm)
- // Exclude trace logs from LSP logs.
- if level < log.Trace {
- ctx = protocol.LogEvent(ctx, ev, lm, messageType(level))
- }
- }
- if i == nil {
- return ctx
- }
- return i.exporter(ctx, ev, lm)
- }
-}
-
-func messageType(l log.Level) protocol.MessageType {
- switch l {
- case log.Error:
- return protocol.Error
- case log.Warning:
- return protocol.Warning
- case log.Debug:
- return protocol.Log
- }
- return protocol.Info
-}
-
-func makeInstanceExporter(i *Instance) event.Exporter {
- exporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
- if i.ocagent != nil {
- ctx = i.ocagent.ProcessEvent(ctx, ev, lm)
- }
- if i.prometheus != nil {
- ctx = i.prometheus.ProcessEvent(ctx, ev, lm)
- }
- if i.rpcs != nil {
- ctx = i.rpcs.ProcessEvent(ctx, ev, lm)
- }
- if i.traces != nil {
- ctx = i.traces.ProcessEvent(ctx, ev, lm)
- }
- if event.IsLog(ev) {
- if s := cache.KeyCreateSession.Get(ev); s != nil {
- i.State.addClient(s)
- }
- if sid := tag.NewServer.Get(ev); sid != "" {
- i.State.updateServer(&Server{
- ID: sid,
- Logfile: tag.Logfile.Get(ev),
- DebugAddress: tag.DebugAddress.Get(ev),
- GoplsPath: tag.GoplsPath.Get(ev),
- ClientID: tag.ClientID.Get(ev),
- })
- }
- if s := cache.KeyShutdownSession.Get(ev); s != nil {
- i.State.dropClient(s)
- }
- if sid := tag.EndServer.Get(ev); sid != "" {
- i.State.dropServer(sid)
- }
- if s := cache.KeyUpdateSession.Get(ev); s != nil {
- if c := i.State.Client(s.ID()); c != nil {
- c.DebugAddress = tag.DebugAddress.Get(ev)
- c.Logfile = tag.Logfile.Get(ev)
- c.ServerID = tag.ServerID.Get(ev)
- c.GoplsPath = tag.GoplsPath.Get(ev)
- }
- }
- }
- if b := tag.Bug.Get(ev); b != "" {
- i.State.recordBug(b, fmt.Sprintf("%v", ev))
- }
- return ctx
- }
- // StdTrace must be above export.Spans below (by convention, export
- // middleware applies its wrapped exporter last).
- exporter = StdTrace(exporter)
- metrics := metric.Config{}
- registerMetrics(&metrics)
- exporter = metrics.Exporter(exporter)
- exporter = export.Spans(exporter)
- exporter = export.Labels(exporter)
- return exporter
-}
-
-type dataFunc func(*http.Request) interface{}
-
-func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) {
- return func(w http.ResponseWriter, r *http.Request) {
- var data interface{}
- if fun != nil {
- data = fun(r)
- }
- if err := tmpl.Execute(w, data); err != nil {
- event.Error(context.Background(), "", err)
- http.Error(w, err.Error(), http.StatusInternalServerError)
- }
- }
-}
-
-func commas(s string) string {
- for i := len(s); i > 3; {
- i -= 3
- s = s[:i] + "," + s[i:]
- }
- return s
-}
-
-func fuint64(v uint64) string {
- return commas(strconv.FormatUint(v, 10))
-}
-
-func fuint32(v uint32) string {
- return commas(strconv.FormatUint(uint64(v), 10))
-}
-
-func fcontent(v []byte) string {
- return string(v)
-}
-
-var BaseTemplate = template.Must(template.New("").Parse(`
-<html>
-<head>
-<title>{{template "title" .}}</title>
-<style>
-.profile-name{
- display:inline-block;
- width:6rem;
-}
-td.value {
- text-align: right;
-}
-ul.events {
- list-style-type: none;
-}
-
-</style>
-{{block "head" .}}{{end}}
-</head>
-<body>
-<a href="/">Main</a>
-<a href="/info">Info</a>
-<a href="/memory">Memory</a>
-<a href="/metrics">Metrics</a>
-<a href="/rpc">RPC</a>
-<a href="/trace">Trace</a>
-<hr>
-<h1>{{template "title" .}}</h1>
-{{block "body" .}}
-Unknown page
-{{end}}
-</body>
-</html>
-
-{{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
-{{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}}
-{{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
-{{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
-{{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
-{{define "filelink"}}<a href="/file/{{.Session}}/{{.FileIdentity.Hash}}">{{.FileIdentity.URI}}</a>{{end}}
-`)).Funcs(template.FuncMap{
- "fuint64": fuint64,
- "fuint32": fuint32,
- "fcontent": fcontent,
- "localAddress": func(s string) string {
- // Try to translate loopback addresses to localhost, both for cosmetics and
- // because unspecified ipv6 addresses can break links on Windows.
- //
- // TODO(rfindley): In the future, it would be better not to assume the
- // server is running on localhost, and instead construct this address using
- // the remote host.
- host, port, err := net.SplitHostPort(s)
- if err != nil {
- return s
- }
- ip := net.ParseIP(host)
- if ip == nil {
- return s
- }
- if ip.IsLoopback() || ip.IsUnspecified() {
- return "localhost:" + port
- }
- return s
- },
- "options": func(s *cache.Session) []sessionOption {
- return showOptions(s.Options())
- },
-})
-
-var MainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}GoPls server information{{end}}
-{{define "body"}}
-<h2>Caches</h2>
-<ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
-<h2>Sessions</h2>
-<ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
-<h2>Views</h2>
-<ul>{{range .State.Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
-<h2>Clients</h2>
-<ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul>
-<h2>Servers</h2>
-<ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
-<h2>Known bugs encountered</h2>
-<dl>{{range .State.Bugs}}<dt>{{.Description}}</dt><dd>{{.Event}}</dd>{{end}}</dl>
-{{end}}
-`))
-
-var InfoTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}GoPls version information{{end}}
-{{define "body"}}
-{{.}}
-{{end}}
-`))
-
-var MemoryTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}GoPls memory usage{{end}}
-{{define "head"}}<meta http-equiv="refresh" content="5">{{end}}
-{{define "body"}}
-<h2>Stats</h2>
-<table>
-<tr><td class="label">Allocated bytes</td><td class="value">{{fuint64 .HeapAlloc}}</td></tr>
-<tr><td class="label">Total allocated bytes</td><td class="value">{{fuint64 .TotalAlloc}}</td></tr>
-<tr><td class="label">System bytes</td><td class="value">{{fuint64 .Sys}}</td></tr>
-<tr><td class="label">Heap system bytes</td><td class="value">{{fuint64 .HeapSys}}</td></tr>
-<tr><td class="label">Malloc calls</td><td class="value">{{fuint64 .Mallocs}}</td></tr>
-<tr><td class="label">Frees</td><td class="value">{{fuint64 .Frees}}</td></tr>
-<tr><td class="label">Idle heap bytes</td><td class="value">{{fuint64 .HeapIdle}}</td></tr>
-<tr><td class="label">In use bytes</td><td class="value">{{fuint64 .HeapInuse}}</td></tr>
-<tr><td class="label">Released to system bytes</td><td class="value">{{fuint64 .HeapReleased}}</td></tr>
-<tr><td class="label">Heap object count</td><td class="value">{{fuint64 .HeapObjects}}</td></tr>
-<tr><td class="label">Stack in use bytes</td><td class="value">{{fuint64 .StackInuse}}</td></tr>
-<tr><td class="label">Stack from system bytes</td><td class="value">{{fuint64 .StackSys}}</td></tr>
-<tr><td class="label">Bucket hash bytes</td><td class="value">{{fuint64 .BuckHashSys}}</td></tr>
-<tr><td class="label">GC metadata bytes</td><td class="value">{{fuint64 .GCSys}}</td></tr>
-<tr><td class="label">Off heap bytes</td><td class="value">{{fuint64 .OtherSys}}</td></tr>
-</table>
-<h2>By size</h2>
-<table>
-<tr><th>Size</th><th>Mallocs</th><th>Frees</th></tr>
-{{range .BySize}}<tr><td class="value">{{fuint32 .Size}}</td><td class="value">{{fuint64 .Mallocs}}</td><td class="value">{{fuint64 .Frees}}</td></tr>{{end}}
-</table>
-{{end}}
-`))
-
-var DebugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}GoPls Debug pages{{end}}
-{{define "body"}}
-<a href="/debug/pprof">Profiling</a>
-{{end}}
-`))
-
-var CacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Cache {{.ID}}{{end}}
-{{define "body"}}
-<h2>memoize.Store entries</h2>
-<ul>{{range $k,$v := .MemStats}}<li>{{$k}} - {{$v}}</li>{{end}}</ul>
-<h2>Per-package usage - not accurate, for guidance only</h2>
-{{.PackageStats true}}
-{{end}}
-`))
-
-var ClientTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Client {{.Session.ID}}{{end}}
-{{define "body"}}
-Using session: <b>{{template "sessionlink" .Session.ID}}</b><br>
-{{if .DebugAddress}}Debug this client at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
-Logfile: {{.Logfile}}<br>
-Gopls Path: {{.GoplsPath}}<br>
-<h2>Diagnostics</h2>
-{{/*Service: []protocol.Server; each server has map[uri]fileReports;
- each fileReport: map[diagnosticSoure]diagnosticReport
- diagnosticSource is one of 5 source
- diagnosticReport: snapshotID and map[hash]*source.Diagnostic
- sourceDiagnostic: struct {
- Range protocol.Range
- Message string
- Source string
- Code string
- CodeHref string
- Severity protocol.DiagnosticSeverity
- Tags []protocol.DiagnosticTag
-
- Related []RelatedInformation
- }
- RelatedInformation: struct {
- URI span.URI
- Range protocol.Range
- Message string
- }
- */}}
-<ul>{{range $k, $v := .Service.Diagnostics}}<li>{{$k}}:<ol>{{range $v}}<li>{{.}}</li>{{end}}</ol></li>{{end}}</ul>
-{{end}}
-`))
-
-var ServerTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Server {{.ID}}{{end}}
-{{define "body"}}
-{{if .DebugAddress}}Debug this server at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
-Logfile: {{.Logfile}}<br>
-Gopls Path: {{.GoplsPath}}<br>
-{{end}}
-`))
-
-var SessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Session {{.ID}}{{end}}
-{{define "body"}}
-From: <b>{{template "cachelink" .Cache.ID}}</b><br>
-<h2>Views</h2>
-<ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} in {{.Folder}}</li>{{end}}</ul>
-<h2>Overlays</h2>
-<ul>{{range .Overlays}}<li>{{template "filelink" .}}</li>{{end}}</ul>
-<h2>Options</h2>
-{{range options .}}
-<p><b>{{.Name}}</b> {{.Type}}</p>
-<p><i>default:</i> {{.Default}}</p>
-{{if ne .Default .Current}}<p><i>current:</i> {{.Current}}</p>{{end}}
-{{end}}
-{{end}}
-`))
-
-var ViewTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}View {{.ID}}{{end}}
-{{define "body"}}
-Name: <b>{{.Name}}</b><br>
-Folder: <b>{{.Folder}}</b><br>
-From: <b>{{template "sessionlink" .Session.ID}}</b><br>
-<h2>Environment</h2>
-<ul>{{range .Options.Env}}<li>{{.}}</li>{{end}}</ul>
-{{end}}
-`))
-
-var FileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Overlay {{.FileIdentity.Hash}}{{end}}
-{{define "body"}}
-{{with .}}
- From: <b>{{template "sessionlink" .Session}}</b><br>
- URI: <b>{{.URI}}</b><br>
- Identifier: <b>{{.FileIdentity.Hash}}</b><br>
- Version: <b>{{.Version}}</b><br>
- Kind: <b>{{.Kind}}</b><br>
-{{end}}
-<h3>Contents</h3>
-<pre>{{fcontent .Read}}</pre>
-{{end}}
-`))