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