godoc/analysis: show analysis status in UI (source file view)

Also:
- declare PackageInfo, FileInfo types to simplify API.
- update docs:
        eliminate this TODO item
        document improved analysis times
        state that -analysis=pointer implies -analysis=type

LGTM=gri
R=gri
CC=golang-codereviews
https://golang.org/cl/112770044
diff --git a/godoc/analysis/README b/godoc/analysis/README
index 6913b97..411af1c 100644
--- a/godoc/analysis/README
+++ b/godoc/analysis/README
@@ -59,7 +59,6 @@
 - Suppress toggle divs for empty method sets.
 
 Misc:
-- Add an "analysis help" page explaining the features and UI in more detail.
 - The [X] button in the lower pane is subject to scrolling.
 - Should the lower pane be floating?  An iframe?
   When we change document.location by clicking on a link, it will go away.
@@ -110,9 +109,3 @@
 filenames).  20% of the HTML is L%d spans (now disabled).  The HTML
 also contains lots of tooltips for long struct/interface types.
 De-dup or just abbreviate?  The actual formatting is very fast.
-
-The pointer analysis constraint solver is way too slow.  We really
-need to implement the constraint optimizer.  Also: performance has
-been quite unpredictable; I recently optimized it fourfold with
-type-based label tracking, but the benefit seems to have evaporated.
-TODO(adonovan): take a look at recent CLs for regressions.
diff --git a/godoc/analysis/analysis.go b/godoc/analysis/analysis.go
index 9fabe11..7bf4dc0 100644
--- a/godoc/analysis/analysis.go
+++ b/godoc/analysis/analysis.go
@@ -123,6 +123,13 @@
 
 // -- fileInfo ---------------------------------------------------------
 
+// FileInfo holds analysis information for the source file view.
+// Clients must not mutate it.
+type FileInfo struct {
+	Data  []interface{} // JSON serializable values
+	Links []Link        // HTML link markup
+}
+
 // A fileInfo is the server's store of hyperlinks and JSON data for a
 // particular file.
 type fileInfo struct {
@@ -154,20 +161,28 @@
 	return index
 }
 
-// get returns new slices containing opaque JSON values and the HTML link markup for fi.
-// Callers must not mutate the elements.
-func (fi *fileInfo) get() (data []interface{}, links []Link) {
+// get returns the file info in external form.
+// Callers must not mutate its fields.
+func (fi *fileInfo) get() FileInfo {
+	var r FileInfo
 	// Copy slices, to avoid races.
 	fi.mu.Lock()
-	data = append(data, fi.data...)
+	r.Data = append(r.Data, fi.data...)
 	if !fi.sorted {
 		sort.Sort(linksByStart(fi.links))
 		fi.sorted = true
 	}
-	links = append(links, fi.links...)
+	r.Links = append(r.Links, fi.links...)
 	fi.mu.Unlock()
+	return r
+}
 
-	return
+// PackageInfo holds analysis information for the package view.
+// Clients must not mutate it.
+type PackageInfo struct {
+	CallGraph      []*PCGNodeJSON
+	CallGraphIndex map[string]int
+	Types          []*TypeInfoJSON
 }
 
 type pkgInfo struct {
@@ -190,16 +205,17 @@
 	pi.mu.Unlock()
 }
 
-// get returns new slices of JSON values for the callgraph and type info for pi.
-// Callers must not mutate the slice elements or the map.
-func (pi *pkgInfo) get() (callGraph []*PCGNodeJSON, callGraphIndex map[string]int, types []*TypeInfoJSON) {
+// get returns the package info in external form.
+// Callers must not mutate its fields.
+func (pi *pkgInfo) get() PackageInfo {
+	var r PackageInfo
 	// Copy slices, to avoid races.
 	pi.mu.Lock()
-	callGraph = append(callGraph, pi.callGraph...)
-	callGraphIndex = pi.callGraphIndex
-	types = append(types, pi.types...)
+	r.CallGraph = append(r.CallGraph, pi.callGraph...)
+	r.CallGraphIndex = pi.callGraphIndex
+	r.Types = append(r.Types, pi.types...)
 	pi.mu.Unlock()
-	return
+	return r
 }
 
 // -- Result -----------------------------------------------------------
@@ -209,6 +225,7 @@
 // and JavaScript data referenced by the links.
 type Result struct {
 	mu        sync.Mutex           // guards maps (but not their contents)
+	status    string               // global analysis status
 	fileInfos map[string]*fileInfo // keys are godoc file URLs
 	pkgInfos  map[string]*pkgInfo  // keys are import paths
 }
@@ -229,12 +246,26 @@
 	return fi
 }
 
+// Status returns a human-readable description of the current analysis status.
+func (res *Result) Status() string {
+	res.mu.Lock()
+	defer res.mu.Unlock()
+	return res.status
+}
+
+func (res *Result) setStatusf(format string, args ...interface{}) {
+	res.mu.Lock()
+	res.status = fmt.Sprintf(format, args...)
+	log.Printf(format, args...)
+	res.mu.Unlock()
+}
+
 // FileInfo returns new slices containing opaque JSON values and the
 // HTML link markup for the specified godoc file URL.  Thread-safe.
 // Callers must not mutate the elements.
 // It returns "zero" if no data is available.
 //
-func (res *Result) FileInfo(url string) ([]interface{}, []Link) {
+func (res *Result) FileInfo(url string) (fi FileInfo) {
 	return res.fileInfo(url).get()
 }
 
@@ -256,10 +287,10 @@
 
 // PackageInfo returns new slices of JSON values for the callgraph and
 // type info for the specified package.  Thread-safe.
-// Callers must not mutate the elements.
+// Callers must not mutate its fields.
 // PackageInfo returns "zero" if no data is available.
 //
-func (res *Result) PackageInfo(importPath string) ([]*PCGNodeJSON, map[string]int, []*TypeInfoJSON) {
+func (res *Result) PackageInfo(importPath string) PackageInfo {
 	return res.pkgInfo(importPath).get()
 }
 
@@ -333,17 +364,18 @@
 	//args = []string{"fmt"}
 
 	if _, err := conf.FromArgs(args, true); err != nil {
-		log.Printf("Analysis failed: %s\n", err) // import error
+		// TODO(adonovan): degrade gracefully, not fail totally.
+		result.setStatusf("Analysis failed: %s.", err) // import error
 		return
 	}
 
-	log.Print("Loading and type-checking packages...")
+	result.setStatusf("Loading and type-checking packages...")
 	iprog, err := conf.Load()
 	if iprog != nil {
 		log.Printf("Loaded %d packages.", len(iprog.AllPackages))
 	}
 	if err != nil {
-		log.Printf("Analysis failed: %s\n", err)
+		result.setStatusf("Loading failed: %s.\n", err)
 		return
 	}
 
@@ -365,9 +397,9 @@
 	log.Print("Main packages: ", mainPkgs)
 
 	// Build SSA code for bodies of all functions in the whole program.
-	log.Print("Building SSA...")
+	result.setStatusf("Constructing SSA form...")
 	prog.BuildAll()
-	log.Print("SSA building complete")
+	log.Print("SSA construction complete")
 
 	a := analysis{
 		result: result,
@@ -443,19 +475,18 @@
 			}
 		}
 	}
-	log.Print("Computing implements...")
+	log.Print("Computing implements relation...")
 	facts := computeImplements(&a.prog.MethodSets, a.allNamed)
 
 	// Add the type-based analysis results.
 	log.Print("Extracting type info...")
-
 	for _, info := range iprog.AllPackages {
 		a.doTypeInfo(info, facts)
 	}
 
 	a.visitInstrs(pta)
 
-	log.Print("Extracting type info complete")
+	result.setStatusf("Type analysis complete.")
 
 	if pta {
 		a.pointer(mainPkgs)
@@ -514,23 +545,24 @@
 	a.ptaConfig.BuildCallGraph = true
 	a.ptaConfig.Reflection = false // (for now)
 
-	log.Print("Running pointer analysis...")
+	a.result.setStatusf("Pointer analysis running...")
+
 	ptares, err := pointer.Analyze(&a.ptaConfig)
 	if err != nil {
 		// If this happens, it indicates a bug.
-		log.Printf("Pointer analysis failed: %s", err)
+		a.result.setStatusf("Pointer analysis failed: %s.", err)
 		return
 	}
 	log.Print("Pointer analysis complete.")
 
 	// Add the results of pointer analysis.
 
-	log.Print("Computing channel peers...")
+	a.result.setStatusf("Computing channel peers...")
 	a.doChannelPeers(ptares.Queries)
-	log.Print("Computing dynamic call graph edges...")
+	a.result.setStatusf("Computing dynamic call graph edges...")
 	a.doCallgraph(ptares.CallGraph)
 
-	log.Print("Done")
+	a.result.setStatusf("Analysis complete.")
 }
 
 type linksByStart []Link
diff --git a/godoc/server.go b/godoc/server.go
index 76fc48b..f81a02d 100644
--- a/godoc/server.go
+++ b/godoc/server.go
@@ -260,14 +260,13 @@
 	}
 
 	// Emit JSON array for type information.
-	// TODO(adonovan): issue a "pending..." message if results not ready.
-	var callGraph []*analysis.PCGNodeJSON
-	var typeInfos []*analysis.TypeInfoJSON
-	callGraph, info.CallGraphIndex, typeInfos = h.c.Analysis.PackageInfo(relpath)
-	info.CallGraph = htmltemplate.JS(marshalJSON(callGraph))
-	info.AnalysisData = htmltemplate.JS(marshalJSON(typeInfos))
+	// TODO(adonovan): display the h.c.Analysis.Status() message in the UI.
+	pi := h.c.Analysis.PackageInfo(relpath)
+	info.CallGraphIndex = pi.CallGraphIndex
+	info.CallGraph = htmltemplate.JS(marshalJSON(pi.CallGraph))
+	info.AnalysisData = htmltemplate.JS(marshalJSON(pi.Types))
 	info.TypeInfoIndex = make(map[string]int)
-	for i, ti := range typeInfos {
+	for i, ti := range pi.Types {
 		info.TypeInfoIndex[ti.Name] = i
 	}
 
@@ -501,20 +500,19 @@
 	var buf bytes.Buffer
 	if pathpkg.Ext(abspath) == ".go" {
 		// Find markup links for this file (e.g. "/src/pkg/fmt/print.go").
-		data, links := p.Corpus.Analysis.FileInfo(abspath)
+		fi := p.Corpus.Analysis.FileInfo(abspath)
 		buf.WriteString("<script type='text/javascript'>document.ANALYSIS_DATA = ")
-		buf.Write(marshalJSON(data))
+		buf.Write(marshalJSON(fi.Data))
 		buf.WriteString(";</script>\n")
 
-		// TODO(adonovan): indicate whether analysis is
-		// disabled, pending, completed or failed.
-		// For now, display help link only if 'completed'.
-		if links != nil {
-			buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a><br/>")
+		if status := p.Corpus.Analysis.Status(); status != "" {
+			buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a> ")
+			// TODO(adonovan): show analysis status at per-file granularity.
+			fmt.Fprintf(&buf, "<span style='color: grey'>[%s]</span><br/>", htmlpkg.EscapeString(status))
 		}
 
 		buf.WriteString("<pre>")
-		formatGoSource(&buf, src, links, h, s)
+		formatGoSource(&buf, src, fi.Links, h, s)
 		buf.WriteString("</pre>")
 	} else {
 		buf.WriteString("<pre>")
diff --git a/godoc/static/analysis/help.html b/godoc/static/analysis/help.html
index 30ed4a6..61f0665 100644
--- a/godoc/static/analysis/help.html
+++ b/godoc/static/analysis/help.html
@@ -39,7 +39,7 @@
   expression and the method set of each type, and determines which
   types are assignable to each interface type.
 
-  <b>Type analysis</b> is relatively quick, requiring just a few seconds for
+  <b>Type analysis</b> is relatively quick, requiring about 10 seconds for
   the &gt;200 packages of the standard library, for example.
 </p>
 
@@ -101,7 +101,7 @@
 
 <h2>Pointer analysis features</h2>
 <p>
-  <code>godoc -analysis=pointer</code> performs a precise
+  <code>godoc -analysis=pointer</code> additionally performs a precise
   whole-program <b>pointer analysis</b>.  In other words, it
   approximates the set of memory locations to which each
   reference&mdash;not just vars of kind <code>*T</code>, but also
@@ -113,10 +113,8 @@
   channel.
 </p>
 <p>
-  <span class='err'>⚠</span> Pointer analysis is currently quite slow,
-  taking around two minutes for the standard library.  This will
-  improve markedly with the planned addition of a constraint
-  optimizer.
+  Pointer analysis is slower than type analysis, taking an additional
+  15 seconds or so for the standard libraries and their tests.
 </p>
 
 <h3>Call graph navigation</h3>
@@ -250,9 +248,6 @@
 
 <h2>Known issues</h2>
 <p>
-  <span class='err'>⚠</span> There is no UI indication of the state of
-  the analysis (pending, complete, failed) during warm-up.</br>
-
   <span class='err'>⚠</span> All analysis results pertain to exactly
   one configuration (e.g. amd64 linux).  Files that are conditionally
   compiled based on different platforms or build tags are not visible
diff --git a/godoc/static/static.go b/godoc/static/static.go
index 6b28883..625ac16 100644
--- a/godoc/static/static.go
+++ b/godoc/static/static.go
@@ -60,7 +60,7 @@
   expression and the method set of each type, and determines which
   types are assignable to each interface type.
 
-  <b>Type analysis</b> is relatively quick, requiring just a few seconds for
+  <b>Type analysis</b> is relatively quick, requiring about 10 seconds for
   the >200 packages of the standard library, for example.
 </p>
 
@@ -122,7 +122,7 @@
 
 <h2>Pointer analysis features</h2>
 <p>
-  <code>godoc -analysis=pointer</code> performs a precise
+  <code>godoc -analysis=pointer</code> additionally performs a precise
   whole-program <b>pointer analysis</b>.  In other words, it
   approximates the set of memory locations to which each
   reference&mdash;not just vars of kind <code>*T</code>, but also
@@ -134,10 +134,8 @@
   channel.
 </p>
 <p>
-  <span class='err'>⚠</span> Pointer analysis is currently quite slow,
-  taking around two minutes for the standard library.  This will
-  improve markedly with the planned addition of a constraint
-  optimizer.
+  Pointer analysis is slower than type analysis, taking an additional
+  15 seconds or so for the standard libraries and their tests.
 </p>
 
 <h3>Call graph navigation</h3>
@@ -271,9 +269,6 @@
 
 <h2>Known issues</h2>
 <p>
-  <span class='err'>⚠</span> There is no UI indication of the state of
-  the analysis (pending, complete, failed) during warm-up.</br>
-
   <span class='err'>⚠</span> All analysis results pertain to exactly
   one configuration (e.g. amd64 linux).  Files that are conditionally
   compiled based on different platforms or build tags are not visible