internal/worker: improve home and debug pages

- Move excluded prefix list to a separate page.
- Move debug pages under "/debug".
  (Previously the dcensus ones weren't even exposed.)
- Add links to debug pages at top of home page.

Change-Id: If26e139984b34cb6b2ef78af8ef154d96436a26a
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/566418
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
kokoro-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
diff --git a/cmd/frontend/main.go b/cmd/frontend/main.go
index 11bcaa7..f1f841b 100644
--- a/cmd/frontend/main.go
+++ b/cmd/frontend/main.go
@@ -199,7 +199,7 @@
 	// We are not currently forwarding any ports on AppEngine, so serving debug
 	// information is broken.
 	if !serverconfig.OnAppEngine() {
-		dcensusServer, err := dcensus.NewServer()
+		dcensusServer, err := dcensus.DebugHandler()
 		if err != nil {
 			log.Fatal(ctx, err)
 		}
diff --git a/cmd/worker/main.go b/cmd/worker/main.go
index 2383130..27b37cc 100644
--- a/cmd/worker/main.go
+++ b/cmd/worker/main.go
@@ -136,15 +136,6 @@
 	if err := dcensus.Init(cfg, views...); err != nil {
 		log.Fatal(ctx, err)
 	}
-	// We are not currently forwarding any ports on AppEngine, so serving debug
-	// information is broken.
-	if !serverconfig.OnAppEngine() {
-		dcensusServer, err := dcensus.NewServer()
-		if err != nil {
-			log.Fatal(ctx, err)
-		}
-		go http.ListenAndServe(cfg.DebugAddr("localhost:8001"), dcensusServer)
-	}
 
 	iap := middleware.Identity()
 	if aud := os.Getenv("GO_DISCOVERY_IAP_AUDIENCE"); aud != "" {
@@ -159,6 +150,12 @@
 	)
 	http.Handle("/", mw(router))
 
+	dh, err := server.DebugHandler()
+	if err != nil {
+		log.Fatal(ctx, err)
+	}
+	http.Handle("/debug/", mw(http.StripPrefix("/debug", dh)))
+
 	addr := cfg.HostAddr("localhost:8000")
 	log.Infof(ctx, "Timeout is %d minutes", timeout)
 	log.Infof(ctx, "Listening on addr %s", addr)
diff --git a/internal/dcensus/dcensus.go b/internal/dcensus/dcensus.go
index f67de36..1447a0d 100644
--- a/internal/dcensus/dcensus.go
+++ b/internal/dcensus/dcensus.go
@@ -71,12 +71,6 @@
 	r.Handle(route, handler)
 }
 
-const debugPage = `
-<html>
-<p><a href="/tracez">/tracez</a> - trace spans</p>
-<p><a href="/statsz">/statz</a> - prometheus metrics page</p>
-`
-
 // Init configures tracing and aggregation according to the given Views. If
 // running on GCP, Init also configures exporting to StackDriver.
 func Init(cfg *config.Config, views ...*view.View) error {
@@ -91,19 +85,15 @@
 	return nil
 }
 
-// NewServer creates a new http.Handler for serving debug information.
-func NewServer() (http.Handler, error) {
+// DebugHandler creates a new http.Handler for serving debug information.
+func DebugHandler() (http.Handler, error) {
 	pe, err := prometheus.NewExporter(prometheus.Options{})
 	if err != nil {
 		return nil, fmt.Errorf("dcensus.NewServer: prometheus.NewExporter: %v", err)
 	}
 	mux := http.NewServeMux()
 	zpages.Handle(mux, "/")
-	mux.Handle("/statsz", pe)
-	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		fmt.Fprint(w, debugPage)
-	})
-
+	mux.Handle("/statz", pe)
 	return mux, nil
 }
 
diff --git a/internal/worker/pages.go b/internal/worker/pages.go
index fbd5401..dd9c13c 100644
--- a/internal/worker/pages.go
+++ b/internal/worker/pages.go
@@ -37,31 +37,16 @@
 // doIndexPage writes the status page. On error it returns the error and a short
 // string to be written back to the client.
 func (s *Server) doIndexPage(w http.ResponseWriter, r *http.Request) (err error) {
+	if r.URL.Path != "/" {
+		http.NotFound(w, r)
+		return nil
+	}
 	defer derrors.Wrap(&err, "doIndexPage")
-	var (
-		experiments []*internal.Experiment
-		excluded    []string
-	)
+	var experiments []*internal.Experiment
 	if s.getExperiments != nil {
 		experiments = s.getExperiments()
 	}
-	g, ctx := errgroup.WithContext(r.Context())
-	g.Go(func() error {
-		var err error
-		excluded, err = s.db.GetExcludedPatterns(ctx)
-		if err != nil {
-			return annotation{err, "error fetching excluded"}
-		}
-		return nil
-	})
-	if err := g.Wait(); err != nil {
-		var e annotation
-		if errors.As(err, &e) {
-			log.Errorf(ctx, e.msg, err)
-		}
-		return err
-	}
-
+	ctx := r.Context()
 	gms := memory.ReadRuntimeStats()
 	sms, err := memory.ReadSystemStats()
 	if err != nil {
@@ -97,7 +82,6 @@
 		Hostname        string
 		StartTime       time.Time
 		Experiments     []*internal.Experiment
-		Excluded        []string
 		LoadShedStats   LoadShedStats
 		GoMemStats      runtime.MemStats
 		ProcessStats    memory.ProcessStats
@@ -114,7 +98,6 @@
 		Hostname:       os.Getenv("HOSTNAME"),
 		StartTime:      startTime,
 		Experiments:    experiments,
-		Excluded:       excluded,
 		LoadShedStats:  s.ZipLoadShedStats(),
 		GoMemStats:     gms,
 		ProcessStats:   pms,
@@ -208,6 +191,20 @@
 	}
 	return renderPage(ctx, w, page, s.templates[versionsTemplate])
 }
+func (s *Server) doExcludedPage(w http.ResponseWriter, r *http.Request) (err error) {
+	excluded, err := s.db.GetExcludedPatterns(r.Context())
+	if err != nil {
+		return annotation{err, "error fetching excluded"}
+	}
+	page := struct {
+		Env      string
+		Excluded []string
+	}{
+		Env:      env(s.cfg),
+		Excluded: excluded,
+	}
+	return renderPage(r.Context(), w, page, s.templates[excludedTemplate])
+}
 
 func env(cfg *config.Config) string {
 	e := cfg.DeploymentEnvironment()
diff --git a/internal/worker/server.go b/internal/worker/server.go
index 1f65091..9b4be95 100644
--- a/internal/worker/server.go
+++ b/internal/worker/server.go
@@ -28,6 +28,7 @@
 	"golang.org/x/pkgsite/internal/cache"
 	"golang.org/x/pkgsite/internal/config"
 	"golang.org/x/pkgsite/internal/config/serverconfig"
+	"golang.org/x/pkgsite/internal/dcensus"
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/godoc/dochtml"
 	"golang.org/x/pkgsite/internal/index"
@@ -77,6 +78,7 @@
 const (
 	indexTemplate    = "index.tmpl"
 	versionsTemplate = "versions.tmpl"
+	excludedTemplate = "excluded.tmpl"
 )
 
 // NewServer creates a new Server with the given dependencies.
@@ -90,12 +92,17 @@
 	if err != nil {
 		return nil, err
 	}
+	t3, err := parseTemplate(scfg.StaticPath, template.TrustedSourceFromConstant(excludedTemplate))
+	if err != nil {
+		return nil, err
+	}
 	ts := template.TrustedSourceJoin(scfg.StaticPath)
 	tfs := template.TrustedFSFromTrustedSource(ts)
 	dochtml.LoadTemplates(tfs)
 	templates := map[string]*template.Template{
 		indexTemplate:    t1,
 		versionsTemplate: t2,
+		excludedTemplate: t3,
 	}
 	var c *cache.Cache
 	if scfg.RedisCacheClient != nil {
@@ -149,7 +156,7 @@
 	// and for /_ah/warmup at
 	// https://cloud.google.com/appengine/docs/standard/go/configuring-warmup-requests.
 	handle("/_ah/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Infof(r.Context(), "Request made to %q", r.URL.Path)
+		log.Errorf(r.Context(), "Request made to %q", r.URL.Path)
 	}))
 
 	// scheduled: poll polls the Module Index for new modules
@@ -239,9 +246,6 @@
 
 	handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(s.staticPath.String()))))
 
-	// returns an HTML page displaying information about recent versions that were processed.
-	handle("/versions", http.HandlerFunc(s.handleHTMLPage(s.doVersionsPage)))
-
 	// Health check.
 	handle("/healthz", http.HandlerFunc(s.handleHealthCheck))
 
@@ -253,6 +257,25 @@
 	handle("/", http.HandlerFunc(s.handleHTMLPage(s.doIndexPage)))
 }
 
+func (s *Server) DebugHandler() (http.Handler, error) {
+
+	// Serve census debug handlers.
+	h, err := dcensus.DebugHandler()
+	if err != nil {
+		return nil, err
+	}
+	mux := http.NewServeMux()
+	mux.Handle("/", h)
+
+	// Serve an HTML page displaying information about recent versions that were processed.
+	mux.Handle("/versions", http.HandlerFunc(s.handleHTMLPage(s.doVersionsPage)))
+
+	// Serve a list of excluded prefixes and module versions.
+	mux.Handle("/excluded", http.HandlerFunc(s.handleHTMLPage(s.doExcludedPage)))
+
+	return mux, nil
+}
+
 // handleUpdateImportedByCount updates imported_by_count for all packages.
 func (s *Server) handleUpdateImportedByCount(w http.ResponseWriter, r *http.Request) error {
 	batchSize := parseIntParam(r, "batch", 1000)
@@ -532,7 +555,7 @@
 func (s *Server) handleHTMLPage(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		if err := f(w, r); err != nil {
-			log.Errorf(r.Context(), "handleHTMLPage", err)
+			log.Errorf(r.Context(), "handleHTMLPage: %v", err)
 			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 		}
 	}
diff --git a/static/worker/excluded.tmpl b/static/worker/excluded.tmpl
new file mode 100644
index 0000000..91e8ae7
--- /dev/null
+++ b/static/worker/excluded.tmpl
@@ -0,0 +1,28 @@
+<!--
+  Copyright 2024 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.
+-->
+
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<link href="/static/worker/worker.min.css" rel="stylesheet">
+<title>{{.Env}} Worker Excluded</title>
+
+<body>
+  <div>
+    <h3>Excluded Prefixes and Versions</h3>
+    {{if .Excluded}}
+      <table>
+        <tbody>
+        {{range .Excluded}}
+          <tr><td>{{.}}</td></tr>
+        {{end}}
+        </tbody>
+      </table>
+    {{else}}
+      <p>No excluded prefixes.</p>
+    {{end}}
+  </div>
+</body>
diff --git a/static/worker/index.tmpl b/static/worker/index.tmpl
index d2e99fb..687f44b 100644
--- a/static/worker/index.tmpl
+++ b/static/worker/index.tmpl
@@ -15,12 +15,6 @@
   <p>All times in America/New_York.</p>
 
   <p>
-    <a href="/">
-      Home
-    </a> |
-    <a href="/versions">
-      Recent Versions
-    </a> |
     <a href="https://cloud.google.com/console/cloudtasks/queue/{{.LocationID}}/{{.ResourcePrefix}}fetch-tasks?project={{.Config.ProjectID}}"
     target="_blank" rel="noreferrer">
      Task Queue
@@ -35,6 +29,14 @@
     </a>
   </p>
 
+  <p>
+    <a href="/debug/versions">Modules</a> |
+    <a href="/debug/tracez">Traces</a> |
+    <a href="/debug/rpcz">RPCs</a> |
+    <a href="/debug/statz">Metrics</a> |
+    <a href="/debug/excluded">Excluded</a>
+  </p>
+
   <div>
     <form action="/poll" method="post" name="pollForm">
       <button title="Poll the module index for up to 2000 new versions."
@@ -181,23 +183,6 @@
     </table>
   </div>
 
-  <div>
-    <h3>Excluded Prefixes</h3>
-    {{if .Excluded}}
-      <table>
-        <thead>
-          <tr><th>Prefix</th></tr>
-        </thead>
-        <tbody>
-        {{range .Excluded}}
-          <tr><td>{{.}}</td></tr>
-        {{end}}
-        </tbody>
-      </table>
-    {{else}}
-      <p>No excluded prefixes.</p>
-    {{end}}
-  </div>
 </body>
 
 <script>