internal/lsp: add debug page serving

This adds a framework for gopls server debugging pages, and adds the standard profiling pages to it.

Change-Id: Ie319e4ad070ac41b2ae7791cb3e0e5bb4ae12ef4
Reviewed-on: https://go-review.googlesource.com/c/tools/+/179277
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/go.mod b/go.mod
index 850cab5..0984a83 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,7 @@
 module golang.org/x/tools
 
+go 1.11
+
 require (
 	golang.org/x/net v0.0.0-20190311183353-d8887717615a
 	golang.org/x/sync v0.0.0-20190423024810-112230192c58
diff --git a/internal/lsp/cmd/serve.go b/internal/lsp/cmd/serve.go
index 28b7398..b24c926 100644
--- a/internal/lsp/cmd/serve.go
+++ b/internal/lsp/cmd/serve.go
@@ -19,6 +19,7 @@
 
 	"golang.org/x/tools/internal/jsonrpc2"
 	"golang.org/x/tools/internal/lsp"
+	"golang.org/x/tools/internal/lsp/debug"
 	"golang.org/x/tools/internal/tool"
 )
 
@@ -30,6 +31,7 @@
 	Port    int    `flag:"port" help:"port on which to run gopls for debugging purposes"`
 	Address string `flag:"listen" help:"address on which to listen for remote connections"`
 	Trace   bool   `flag:"rpc.trace" help:"Print the full rpc trace in lsp inspector format"`
+	Debug   string `flag:"debug" help:"Serve debug information on the supplied address"`
 
 	app *Application
 }
@@ -69,6 +71,9 @@
 		log.SetOutput(io.MultiWriter(os.Stderr, f))
 		out = f
 	}
+
+	debug.Serve(ctx, s.Debug)
+
 	if s.app.Remote != "" {
 		return s.forward()
 	}
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
new file mode 100644
index 0000000..bbe34e0
--- /dev/null
+++ b/internal/lsp/debug/serve.go
@@ -0,0 +1,88 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package debug
+
+import (
+	"context"
+	"html/template"
+	"log"
+	"net"
+	"net/http"
+	_ "net/http/pprof" // pull in the standard pprof handlers
+)
+
+func init() {
+	http.HandleFunc("/", Render(mainTmpl, nil))
+	http.HandleFunc("/debug/", Render(debugTmpl, nil))
+}
+
+// Serve starts and runs a debug server in the background.
+// It also logs the port the server starts on, to allow for :0 auto assigned
+// ports.
+func Serve(ctx context.Context, addr string) error {
+	if addr == "" {
+		return nil
+	}
+	listener, err := net.Listen("tcp", addr)
+	if err != nil {
+		return err
+	}
+	log.Printf("Debug serving on port: %d", listener.Addr().(*net.TCPAddr).Port)
+	go func() {
+		if err := http.Serve(listener, nil); err != nil {
+			log.Printf("Debug server failed with %v", err)
+			return
+		}
+		log.Printf("Debug server finished")
+	}()
+	return nil
+}
+
+func Render(tmpl *template.Template, fun func(*http.Request) interface{}) func(http.ResponseWriter, *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var data interface{}
+		if fun != nil {
+			data = fun(r)
+		}
+		if err := tmpl.Execute(w, data); err != nil {
+			log.Print(err)
+		}
+	}
+}
+
+var BaseTemplate = template.Must(template.New("").Parse(`
+<html>
+<head>
+<title>{{template "title"}}</title>
+<style>
+.profile-name{
+	display:inline-block;
+	width:6rem;
+}
+</style>
+</head>
+<body>
+{{template "title"}}
+<br>
+{{block "body" .Data}}
+Unknown page
+{{end}}
+</body>
+</html>
+`))
+
+var mainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+{{define "title"}}GoPls server information{{end}}
+{{define "body"}}
+<A href="/debug/">Debug</A>
+{{end}}
+`))
+
+var debugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+{{define "title"}}GoPls Debug pages{{end}}
+{{define "body"}}
+<A href="/debug/pprof">Profiling</A>
+{{end}}
+`))