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