diff options
Diffstat (limited to 'internal/lsp/debug/serve.go')
-rw-r--r-- | internal/lsp/debug/serve.go | 954 |
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}} -`)) |