golang.org/x/tools/gopls@v0.15.3/internal/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 "bytes" 9 "context" 10 "errors" 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 "strconv" 23 "strings" 24 "sync" 25 "time" 26 27 "golang.org/x/tools/gopls/internal/cache" 28 "golang.org/x/tools/gopls/internal/debug/log" 29 "golang.org/x/tools/gopls/internal/protocol" 30 "golang.org/x/tools/gopls/internal/util/bug" 31 "golang.org/x/tools/internal/event" 32 "golang.org/x/tools/internal/event/core" 33 "golang.org/x/tools/internal/event/export" 34 "golang.org/x/tools/internal/event/export/metric" 35 "golang.org/x/tools/internal/event/export/ocagent" 36 "golang.org/x/tools/internal/event/export/prometheus" 37 "golang.org/x/tools/internal/event/keys" 38 "golang.org/x/tools/internal/event/label" 39 "golang.org/x/tools/internal/event/tag" 40 ) 41 42 type contextKeyType int 43 44 const ( 45 instanceKey contextKeyType = iota 46 traceKey 47 ) 48 49 // An Instance holds all debug information associated with a gopls instance. 50 type Instance struct { 51 Logfile string 52 StartTime time.Time 53 ServerAddress string 54 OCAgentConfig string 55 56 LogWriter io.Writer 57 58 exporter event.Exporter 59 60 ocagent *ocagent.Exporter 61 prometheus *prometheus.Exporter 62 rpcs *Rpcs 63 traces *traces 64 State *State 65 66 serveMu sync.Mutex 67 debugAddress string 68 listenedDebugAddress string 69 } 70 71 // State holds debugging information related to the server state. 72 type State struct { 73 mu sync.Mutex 74 clients []*Client 75 servers []*Server 76 } 77 78 func (st *State) Bugs() []bug.Bug { 79 return bug.List() 80 } 81 82 // Caches returns the set of Cache objects currently being served. 83 func (st *State) Caches() []*cache.Cache { 84 var caches []*cache.Cache 85 seen := make(map[string]struct{}) 86 for _, client := range st.Clients() { 87 cache := client.Session.Cache() 88 if _, found := seen[cache.ID()]; found { 89 continue 90 } 91 seen[cache.ID()] = struct{}{} 92 caches = append(caches, cache) 93 } 94 return caches 95 } 96 97 // Cache returns the Cache that matches the supplied id. 98 func (st *State) Cache(id string) *cache.Cache { 99 for _, c := range st.Caches() { 100 if c.ID() == id { 101 return c 102 } 103 } 104 return nil 105 } 106 107 // Analysis returns the global Analysis template value. 108 func (st *State) Analysis() (_ analysisTmpl) { return } 109 110 type analysisTmpl struct{} 111 112 func (analysisTmpl) AnalyzerRunTimes() []cache.LabelDuration { return cache.AnalyzerRunTimes() } 113 114 // Sessions returns the set of Session objects currently being served. 115 func (st *State) Sessions() []*cache.Session { 116 var sessions []*cache.Session 117 for _, client := range st.Clients() { 118 sessions = append(sessions, client.Session) 119 } 120 return sessions 121 } 122 123 // Session returns the Session that matches the supplied id. 124 func (st *State) Session(id string) *cache.Session { 125 for _, s := range st.Sessions() { 126 if s.ID() == id { 127 return s 128 } 129 } 130 return nil 131 } 132 133 // Views returns the set of View objects currently being served. 134 func (st *State) Views() []*cache.View { 135 var views []*cache.View 136 for _, s := range st.Sessions() { 137 views = append(views, s.Views()...) 138 } 139 return views 140 } 141 142 // View returns the View that matches the supplied id. 143 func (st *State) View(id string) *cache.View { 144 for _, v := range st.Views() { 145 if v.ID() == id { 146 return v 147 } 148 } 149 return nil 150 } 151 152 // Clients returns the set of Clients currently being served. 153 func (st *State) Clients() []*Client { 154 st.mu.Lock() 155 defer st.mu.Unlock() 156 clients := make([]*Client, len(st.clients)) 157 copy(clients, st.clients) 158 return clients 159 } 160 161 // Client returns the Client matching the supplied id. 162 func (st *State) Client(id string) *Client { 163 for _, c := range st.Clients() { 164 if c.Session.ID() == id { 165 return c 166 } 167 } 168 return nil 169 } 170 171 // Servers returns the set of Servers the instance is currently connected to. 172 func (st *State) Servers() []*Server { 173 st.mu.Lock() 174 defer st.mu.Unlock() 175 servers := make([]*Server, len(st.servers)) 176 copy(servers, st.servers) 177 return servers 178 } 179 180 // A Client is an incoming connection from a remote client. 181 type Client struct { 182 Session *cache.Session 183 DebugAddress string 184 Logfile string 185 GoplsPath string 186 ServerID string 187 Service protocol.Server 188 } 189 190 // A Server is an outgoing connection to a remote LSP server. 191 type Server struct { 192 ID string 193 DebugAddress string 194 Logfile string 195 GoplsPath string 196 ClientID string 197 } 198 199 // addClient adds a client to the set being served. 200 func (st *State) addClient(session *cache.Session) { 201 st.mu.Lock() 202 defer st.mu.Unlock() 203 st.clients = append(st.clients, &Client{Session: session}) 204 } 205 206 // dropClient removes a client from the set being served. 207 func (st *State) dropClient(session *cache.Session) { 208 st.mu.Lock() 209 defer st.mu.Unlock() 210 for i, c := range st.clients { 211 if c.Session == session { 212 copy(st.clients[i:], st.clients[i+1:]) 213 st.clients[len(st.clients)-1] = nil 214 st.clients = st.clients[:len(st.clients)-1] 215 return 216 } 217 } 218 } 219 220 // updateServer updates a server to the set being queried. In practice, there should 221 // be at most one remote server. 222 func (st *State) updateServer(server *Server) { 223 st.mu.Lock() 224 defer st.mu.Unlock() 225 for i, existing := range st.servers { 226 if existing.ID == server.ID { 227 // Replace, rather than mutate, to avoid a race. 228 newServers := make([]*Server, len(st.servers)) 229 copy(newServers, st.servers[:i]) 230 newServers[i] = server 231 copy(newServers[i+1:], st.servers[i+1:]) 232 st.servers = newServers 233 return 234 } 235 } 236 st.servers = append(st.servers, server) 237 } 238 239 // dropServer drops a server from the set being queried. 240 func (st *State) dropServer(id string) { 241 st.mu.Lock() 242 defer st.mu.Unlock() 243 for i, s := range st.servers { 244 if s.ID == id { 245 copy(st.servers[i:], st.servers[i+1:]) 246 st.servers[len(st.servers)-1] = nil 247 st.servers = st.servers[:len(st.servers)-1] 248 return 249 } 250 } 251 } 252 253 // an http.ResponseWriter that filters writes 254 type filterResponse struct { 255 w http.ResponseWriter 256 edit func([]byte) []byte 257 } 258 259 func (c filterResponse) Header() http.Header { 260 return c.w.Header() 261 } 262 263 func (c filterResponse) Write(buf []byte) (int, error) { 264 ans := c.edit(buf) 265 return c.w.Write(ans) 266 } 267 268 func (c filterResponse) WriteHeader(n int) { 269 c.w.WriteHeader(n) 270 } 271 272 // replace annoying nuls by spaces 273 func cmdline(w http.ResponseWriter, r *http.Request) { 274 fake := filterResponse{ 275 w: w, 276 edit: func(buf []byte) []byte { 277 return bytes.ReplaceAll(buf, []byte{0}, []byte{' '}) 278 }, 279 } 280 pprof.Cmdline(fake, r) 281 } 282 283 func (i *Instance) getCache(r *http.Request) interface{} { 284 return i.State.Cache(path.Base(r.URL.Path)) 285 } 286 287 func (i *Instance) getAnalysis(r *http.Request) interface{} { 288 return i.State.Analysis() 289 } 290 291 func (i *Instance) getSession(r *http.Request) interface{} { 292 return i.State.Session(path.Base(r.URL.Path)) 293 } 294 295 func (i *Instance) getClient(r *http.Request) interface{} { 296 return i.State.Client(path.Base(r.URL.Path)) 297 } 298 299 func (i *Instance) getServer(r *http.Request) interface{} { 300 i.State.mu.Lock() 301 defer i.State.mu.Unlock() 302 id := path.Base(r.URL.Path) 303 for _, s := range i.State.servers { 304 if s.ID == id { 305 return s 306 } 307 } 308 return nil 309 } 310 311 func (i *Instance) getView(r *http.Request) interface{} { 312 return i.State.View(path.Base(r.URL.Path)) 313 } 314 315 func (i *Instance) getFile(r *http.Request) interface{} { 316 identifier := path.Base(r.URL.Path) 317 sid := path.Base(path.Dir(r.URL.Path)) 318 s := i.State.Session(sid) 319 if s == nil { 320 return nil 321 } 322 for _, o := range s.Overlays() { 323 // TODO(adonovan): understand and document this comparison. 324 if o.Identity().Hash.String() == identifier { 325 return o 326 } 327 } 328 return nil 329 } 330 331 func (i *Instance) getInfo(r *http.Request) interface{} { 332 buf := &bytes.Buffer{} 333 i.PrintServerInfo(r.Context(), buf) 334 return template.HTML(buf.String()) 335 } 336 337 func (i *Instance) AddService(s protocol.Server, session *cache.Session) { 338 for _, c := range i.State.clients { 339 if c.Session == session { 340 c.Service = s 341 return 342 } 343 } 344 stdlog.Printf("unable to find a Client to add the protocol.Server to") 345 } 346 347 func getMemory(_ *http.Request) interface{} { 348 var m runtime.MemStats 349 runtime.ReadMemStats(&m) 350 return m 351 } 352 353 func init() { 354 event.SetExporter(makeGlobalExporter(os.Stderr)) 355 } 356 357 func GetInstance(ctx context.Context) *Instance { 358 if ctx == nil { 359 return nil 360 } 361 v := ctx.Value(instanceKey) 362 if v == nil { 363 return nil 364 } 365 return v.(*Instance) 366 } 367 368 // WithInstance creates debug instance ready for use using the supplied 369 // configuration and stores it in the returned context. 370 func WithInstance(ctx context.Context, agent string) context.Context { 371 i := &Instance{ 372 StartTime: time.Now(), 373 OCAgentConfig: agent, 374 } 375 i.LogWriter = os.Stderr 376 ocConfig := ocagent.Discover() 377 //TODO: we should not need to adjust the discovered configuration 378 ocConfig.Address = i.OCAgentConfig 379 i.ocagent = ocagent.Connect(ocConfig) 380 i.prometheus = prometheus.New() 381 i.rpcs = &Rpcs{} 382 i.traces = &traces{} 383 i.State = &State{} 384 i.exporter = makeInstanceExporter(i) 385 return context.WithValue(ctx, instanceKey, i) 386 } 387 388 // SetLogFile sets the logfile for use with this instance. 389 func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) { 390 // TODO: probably a better solution for deferring closure to the caller would 391 // be for the debug instance to itself be closed, but this fixes the 392 // immediate bug of logs not being captured. 393 closeLog := func() {} 394 if logfile != "" { 395 if logfile == "auto" { 396 if isDaemon { 397 logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-daemon-%d.log", os.Getpid())) 398 } else { 399 logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid())) 400 } 401 } 402 f, err := os.Create(logfile) 403 if err != nil { 404 return nil, fmt.Errorf("unable to create log file: %w", err) 405 } 406 closeLog = func() { 407 defer f.Close() 408 } 409 stdlog.SetOutput(io.MultiWriter(os.Stderr, f)) 410 i.LogWriter = f 411 } 412 i.Logfile = logfile 413 return closeLog, nil 414 } 415 416 // Serve starts and runs a debug server in the background on the given addr. 417 // It also logs the port the server starts on, to allow for :0 auto assigned 418 // ports. 419 func (i *Instance) Serve(ctx context.Context, addr string) (string, error) { 420 stdlog.SetFlags(stdlog.Lshortfile) 421 if addr == "" { 422 return "", nil 423 } 424 i.serveMu.Lock() 425 defer i.serveMu.Unlock() 426 427 if i.listenedDebugAddress != "" { 428 // Already serving. Return the bound address. 429 return i.listenedDebugAddress, nil 430 } 431 432 i.debugAddress = addr 433 listener, err := net.Listen("tcp", i.debugAddress) 434 if err != nil { 435 return "", err 436 } 437 i.listenedDebugAddress = listener.Addr().String() 438 439 port := listener.Addr().(*net.TCPAddr).Port 440 if strings.HasSuffix(i.debugAddress, ":0") { 441 stdlog.Printf("debug server listening at http://localhost:%d", port) 442 } 443 event.Log(ctx, "Debug serving", tag.Port.Of(port)) 444 go func() { 445 mux := http.NewServeMux() 446 mux.HandleFunc("/", render(MainTmpl, func(*http.Request) interface{} { return i })) 447 mux.HandleFunc("/debug/", render(DebugTmpl, nil)) 448 mux.HandleFunc("/debug/pprof/", pprof.Index) 449 mux.HandleFunc("/debug/pprof/cmdline", cmdline) 450 mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 451 mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 452 mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 453 if i.prometheus != nil { 454 mux.HandleFunc("/metrics/", i.prometheus.Serve) 455 } 456 if i.rpcs != nil { 457 mux.HandleFunc("/rpc/", render(RPCTmpl, i.rpcs.getData)) 458 } 459 if i.traces != nil { 460 mux.HandleFunc("/trace/", render(TraceTmpl, i.traces.getData)) 461 } 462 mux.HandleFunc("/analysis/", render(AnalysisTmpl, i.getAnalysis)) 463 mux.HandleFunc("/cache/", render(CacheTmpl, i.getCache)) 464 mux.HandleFunc("/session/", render(SessionTmpl, i.getSession)) 465 mux.HandleFunc("/client/", render(ClientTmpl, i.getClient)) 466 mux.HandleFunc("/server/", render(ServerTmpl, i.getServer)) 467 mux.HandleFunc("/file/", render(FileTmpl, i.getFile)) 468 mux.HandleFunc("/info", render(InfoTmpl, i.getInfo)) 469 mux.HandleFunc("/memory", render(MemoryTmpl, getMemory)) 470 471 // Internal debugging helpers. 472 mux.HandleFunc("/gc", func(w http.ResponseWriter, r *http.Request) { 473 runtime.GC() 474 runtime.GC() 475 runtime.GC() 476 http.Redirect(w, r, "/memory", http.StatusTemporaryRedirect) 477 }) 478 mux.HandleFunc("/_makeabug", func(w http.ResponseWriter, r *http.Request) { 479 bug.Report("bug here") 480 http.Error(w, "made a bug", http.StatusOK) 481 }) 482 483 if err := http.Serve(listener, mux); err != nil { 484 event.Error(ctx, "Debug server failed", err) 485 return 486 } 487 event.Log(ctx, "Debug server finished") 488 }() 489 return i.listenedDebugAddress, nil 490 } 491 492 func (i *Instance) DebugAddress() string { 493 i.serveMu.Lock() 494 defer i.serveMu.Unlock() 495 return i.debugAddress 496 } 497 498 func (i *Instance) ListenedDebugAddress() string { 499 i.serveMu.Lock() 500 defer i.serveMu.Unlock() 501 return i.listenedDebugAddress 502 } 503 504 func makeGlobalExporter(stderr io.Writer) event.Exporter { 505 p := export.Printer{} 506 var pMu sync.Mutex 507 return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { 508 i := GetInstance(ctx) 509 510 if event.IsLog(ev) { 511 // Don't log context cancellation errors. 512 if err := keys.Err.Get(ev); errors.Is(err, context.Canceled) { 513 return ctx 514 } 515 // Make sure any log messages without an instance go to stderr. 516 if i == nil { 517 pMu.Lock() 518 p.WriteEvent(stderr, ev, lm) 519 pMu.Unlock() 520 } 521 level := log.LabeledLevel(lm) 522 // Exclude trace logs from LSP logs. 523 if level < log.Trace { 524 ctx = protocol.LogEvent(ctx, ev, lm, messageType(level)) 525 } 526 } 527 if i == nil { 528 return ctx 529 } 530 return i.exporter(ctx, ev, lm) 531 } 532 } 533 534 func messageType(l log.Level) protocol.MessageType { 535 switch l { 536 case log.Error: 537 return protocol.Error 538 case log.Warning: 539 return protocol.Warning 540 case log.Debug: 541 return protocol.Log 542 } 543 return protocol.Info 544 } 545 546 func makeInstanceExporter(i *Instance) event.Exporter { 547 exporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context { 548 if i.ocagent != nil { 549 ctx = i.ocagent.ProcessEvent(ctx, ev, lm) 550 } 551 if i.prometheus != nil { 552 ctx = i.prometheus.ProcessEvent(ctx, ev, lm) 553 } 554 if i.rpcs != nil { 555 ctx = i.rpcs.ProcessEvent(ctx, ev, lm) 556 } 557 if i.traces != nil { 558 ctx = i.traces.ProcessEvent(ctx, ev, lm) 559 } 560 if event.IsLog(ev) { 561 if s := cache.KeyCreateSession.Get(ev); s != nil { 562 i.State.addClient(s) 563 } 564 if sid := tag.NewServer.Get(ev); sid != "" { 565 i.State.updateServer(&Server{ 566 ID: sid, 567 Logfile: tag.Logfile.Get(ev), 568 DebugAddress: tag.DebugAddress.Get(ev), 569 GoplsPath: tag.GoplsPath.Get(ev), 570 ClientID: tag.ClientID.Get(ev), 571 }) 572 } 573 if s := cache.KeyShutdownSession.Get(ev); s != nil { 574 i.State.dropClient(s) 575 } 576 if sid := tag.EndServer.Get(ev); sid != "" { 577 i.State.dropServer(sid) 578 } 579 if s := cache.KeyUpdateSession.Get(ev); s != nil { 580 if c := i.State.Client(s.ID()); c != nil { 581 c.DebugAddress = tag.DebugAddress.Get(ev) 582 c.Logfile = tag.Logfile.Get(ev) 583 c.ServerID = tag.ServerID.Get(ev) 584 c.GoplsPath = tag.GoplsPath.Get(ev) 585 } 586 } 587 } 588 return ctx 589 } 590 // StdTrace must be above export.Spans below (by convention, export 591 // middleware applies its wrapped exporter last). 592 exporter = StdTrace(exporter) 593 metrics := metric.Config{} 594 registerMetrics(&metrics) 595 exporter = metrics.Exporter(exporter) 596 exporter = export.Spans(exporter) 597 exporter = export.Labels(exporter) 598 return exporter 599 } 600 601 type dataFunc func(*http.Request) interface{} 602 603 func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) { 604 return func(w http.ResponseWriter, r *http.Request) { 605 var data interface{} 606 if fun != nil { 607 data = fun(r) 608 } 609 if err := tmpl.Execute(w, data); err != nil { 610 event.Error(context.Background(), "", err) 611 http.Error(w, err.Error(), http.StatusInternalServerError) 612 } 613 } 614 } 615 616 func commas(s string) string { 617 for i := len(s); i > 3; { 618 i -= 3 619 s = s[:i] + "," + s[i:] 620 } 621 return s 622 } 623 624 func fuint64(v uint64) string { 625 return commas(strconv.FormatUint(v, 10)) 626 } 627 628 func fuint32(v uint32) string { 629 return commas(strconv.FormatUint(uint64(v), 10)) 630 } 631 632 func fcontent(v []byte) string { 633 return string(v) 634 } 635 636 var BaseTemplate = template.Must(template.New("").Parse(` 637 <html> 638 <head> 639 <title>{{template "title" .}}</title> 640 <style> 641 .profile-name{ 642 display:inline-block; 643 width:6rem; 644 } 645 td.value { 646 text-align: right; 647 } 648 ul.spans { 649 font-family: monospace; 650 font-size: 85%; 651 } 652 body { 653 font-family: sans-serif; 654 font-size: 1rem; 655 line-height: normal; 656 } 657 </style> 658 {{block "head" .}}{{end}} 659 </head> 660 <body> 661 <a href="/">Main</a> 662 <a href="/info">Info</a> 663 <a href="/memory">Memory</a> 664 <a href="/debug/pprof">Profiling</a> 665 <a href="/metrics">Metrics</a> 666 <a href="/rpc">RPC</a> 667 <a href="/trace">Trace</a> 668 <a href="/analysis">Analysis</a> 669 <hr> 670 <h1>{{template "title" .}}</h1> 671 {{block "body" .}} 672 Unknown page 673 {{end}} 674 </body> 675 </html> 676 677 {{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}} 678 {{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}} 679 {{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}} 680 {{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}} 681 `)).Funcs(template.FuncMap{ 682 "fuint64": fuint64, 683 "fuint32": fuint32, 684 "fcontent": fcontent, 685 "localAddress": func(s string) string { 686 // Try to translate loopback addresses to localhost, both for cosmetics and 687 // because unspecified ipv6 addresses can break links on Windows. 688 // 689 // TODO(rfindley): In the future, it would be better not to assume the 690 // server is running on localhost, and instead construct this address using 691 // the remote host. 692 host, port, err := net.SplitHostPort(s) 693 if err != nil { 694 return s 695 } 696 ip := net.ParseIP(host) 697 if ip == nil { 698 return s 699 } 700 if ip.IsLoopback() || ip.IsUnspecified() { 701 return "localhost:" + port 702 } 703 return s 704 }, 705 // TODO(rfindley): re-enable option inspection. 706 // "options": func(s *cache.Session) []sessionOption { 707 // return showOptions(s.Options()) 708 // }, 709 }) 710 711 var MainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 712 {{define "title"}}Gopls server information{{end}} 713 {{define "body"}} 714 <h2>Caches</h2> 715 <ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul> 716 <h2>Sessions</h2> 717 <ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul> 718 <h2>Clients</h2> 719 <ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul> 720 <h2>Servers</h2> 721 <ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul> 722 <h2>Bug reports</h2> 723 <dl>{{range .State.Bugs}}<dt>{{.Key}}</dt><dd>{{.Description}}</dd>{{end}}</dl> 724 {{end}} 725 `)) 726 727 var InfoTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 728 {{define "title"}}Gopls version information{{end}} 729 {{define "body"}} 730 {{.}} 731 {{end}} 732 `)) 733 734 var MemoryTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 735 {{define "title"}}Gopls memory usage{{end}} 736 {{define "head"}}<meta http-equiv="refresh" content="5">{{end}} 737 {{define "body"}} 738 <form action="/gc"><input type="submit" value="Run garbage collector"/></form> 739 <h2>Stats</h2> 740 <table> 741 <tr><td class="label">Allocated bytes</td><td class="value">{{fuint64 .HeapAlloc}}</td></tr> 742 <tr><td class="label">Total allocated bytes</td><td class="value">{{fuint64 .TotalAlloc}}</td></tr> 743 <tr><td class="label">System bytes</td><td class="value">{{fuint64 .Sys}}</td></tr> 744 <tr><td class="label">Heap system bytes</td><td class="value">{{fuint64 .HeapSys}}</td></tr> 745 <tr><td class="label">Malloc calls</td><td class="value">{{fuint64 .Mallocs}}</td></tr> 746 <tr><td class="label">Frees</td><td class="value">{{fuint64 .Frees}}</td></tr> 747 <tr><td class="label">Idle heap bytes</td><td class="value">{{fuint64 .HeapIdle}}</td></tr> 748 <tr><td class="label">In use bytes</td><td class="value">{{fuint64 .HeapInuse}}</td></tr> 749 <tr><td class="label">Released to system bytes</td><td class="value">{{fuint64 .HeapReleased}}</td></tr> 750 <tr><td class="label">Heap object count</td><td class="value">{{fuint64 .HeapObjects}}</td></tr> 751 <tr><td class="label">Stack in use bytes</td><td class="value">{{fuint64 .StackInuse}}</td></tr> 752 <tr><td class="label">Stack from system bytes</td><td class="value">{{fuint64 .StackSys}}</td></tr> 753 <tr><td class="label">Bucket hash bytes</td><td class="value">{{fuint64 .BuckHashSys}}</td></tr> 754 <tr><td class="label">GC metadata bytes</td><td class="value">{{fuint64 .GCSys}}</td></tr> 755 <tr><td class="label">Off heap bytes</td><td class="value">{{fuint64 .OtherSys}}</td></tr> 756 </table> 757 <h2>By size</h2> 758 <table> 759 <tr><th>Size</th><th>Mallocs</th><th>Frees</th></tr> 760 {{range .BySize}}<tr><td class="value">{{fuint32 .Size}}</td><td class="value">{{fuint64 .Mallocs}}</td><td class="value">{{fuint64 .Frees}}</td></tr>{{end}} 761 </table> 762 {{end}} 763 `)) 764 765 var DebugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 766 {{define "title"}}GoPls Debug pages{{end}} 767 {{define "body"}} 768 <a href="/debug/pprof">Profiling</a> 769 {{end}} 770 `)) 771 772 var CacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 773 {{define "title"}}Cache {{.ID}}{{end}} 774 {{define "body"}} 775 <h2>memoize.Store entries</h2> 776 <ul>{{range $k,$v := .MemStats}}<li>{{$k}} - {{$v}}</li>{{end}}</ul> 777 <h2>File stats</h2> 778 <p> 779 {{- $stats := .FileStats -}} 780 Total: <b>{{$stats.Total}}</b><br> 781 Largest: <b>{{$stats.Largest}}</b><br> 782 Errors: <b>{{$stats.Errs}}</b><br> 783 </p> 784 {{end}} 785 `)) 786 787 var AnalysisTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 788 {{define "title"}}Analysis{{end}} 789 {{define "body"}} 790 <h2>Analyzer.Run times</h2> 791 <ul>{{range .AnalyzerRunTimes}}<li>{{.Duration}} {{.Label}}</li>{{end}}</ul> 792 {{end}} 793 `)) 794 795 var ClientTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 796 {{define "title"}}Client {{.Session.ID}}{{end}} 797 {{define "body"}} 798 Using session: <b>{{template "sessionlink" .Session.ID}}</b><br> 799 {{if .DebugAddress}}Debug this client at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}} 800 Logfile: {{.Logfile}}<br> 801 Gopls Path: {{.GoplsPath}}<br> 802 {{end}} 803 `)) 804 805 var ServerTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 806 {{define "title"}}Server {{.ID}}{{end}} 807 {{define "body"}} 808 {{if .DebugAddress}}Debug this server at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}} 809 Logfile: {{.Logfile}}<br> 810 Gopls Path: {{.GoplsPath}}<br> 811 {{end}} 812 `)) 813 814 var SessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 815 {{define "title"}}Session {{.ID}}{{end}} 816 {{define "body"}} 817 From: <b>{{template "cachelink" .Cache.ID}}</b><br> 818 <h2>Views</h2> 819 <ul>{{range .Views}} 820 {{- $envOverlay := .EnvOverlay -}} 821 <li>ID: <b>{{.ID}}</b><br> 822 Type: <b>{{.Type}}</b><br> 823 Root: <b>{{.Root}}</b><br> 824 {{- if $envOverlay}} 825 Env overlay: <b>{{$envOverlay}})</b><br> 826 {{end -}} 827 Folder: <b>{{.Folder.Name}}:{{.Folder.Dir}}</b></li> 828 {{end}}</ul> 829 <h2>Overlays</h2> 830 {{$session := .}} 831 <ul>{{range .Overlays}} 832 <li> 833 <a href="/file/{{$session.ID}}/{{.Identity.Hash}}">{{.Identity.URI}}</a> 834 </li>{{end}}</ul> 835 {{end}} 836 `)) 837 838 var FileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 839 {{define "title"}}Overlay {{.Identity.Hash}}{{end}} 840 {{define "body"}} 841 {{with .}} 842 URI: <b>{{.URI}}</b><br> 843 Identifier: <b>{{.Identity.Hash}}</b><br> 844 Version: <b>{{.Version}}</b><br> 845 Kind: <b>{{.Kind}}</b><br> 846 {{end}} 847 <h3>Contents</h3> 848 <pre>{{fcontent .Content}}</pre> 849 {{end}} 850 `))