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