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 `))