From 99d45c0e8ea8b33f08ff4481a5b61ca254a80477 Mon Sep 17 00:00:00 2001
From: Alan Donovan
Date: Wed, 9 Jul 2014 07:59:55 -0400
Subject: [PATCH] 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
---
godoc/analysis/README | 7 ---
godoc/analysis/analysis.go | 90 ++++++++++++++++++++++-----------
godoc/server.go | 28 +++++-----
godoc/static/analysis/help.html | 13 ++---
godoc/static/static.go | 13 ++---
5 files changed, 82 insertions(+), 69 deletions(-)
diff --git a/godoc/analysis/README b/godoc/analysis/README
index 6913b9796d..411af1cbaf 100644
--- a/godoc/analysis/README
+++ b/godoc/analysis/README
@@ -59,7 +59,6 @@ Type info:
- 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 @@ contains a lot of filenames (e.g. 820 copies of 16 distinct
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 9fabe118ca..7bf4dc0fe3 100644
--- a/godoc/analysis/analysis.go
+++ b/godoc/analysis/analysis.go
@@ -123,6 +123,13 @@ func (e errorLink) Write(w io.Writer, _ int, start bool) {
// -- 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 @@ func (fi *fileInfo) addData(x interface{}) int {
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 @@ func (pi *pkgInfo) addType(t *TypeInfoJSON) {
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 @@ func (pi *pkgInfo) get() (callGraph []*PCGNodeJSON, callGraphIndex map[string]in
// 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 @@ func (res *Result) fileInfo(url string) *fileInfo {
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 @@ func (res *Result) pkgInfo(importPath string) *pkgInfo {
// 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 @@ func Run(pta bool, result *Result) {
//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 @@ func Run(pta bool, result *Result) {
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 @@ func Run(pta bool, result *Result) {
}
}
}
- 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 @@ func (a *analysis) pointer(mainPkgs []*ssa.Package) {
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 76fc48b45d..f81a02d778 100644
--- a/godoc/server.go
+++ b/godoc/server.go
@@ -260,14 +260,13 @@ func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// 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 @@ func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abs
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("\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("Static analysis features
")
+ if status := p.Corpus.Analysis.Status(); status != "" {
+ buf.WriteString("Static analysis features ")
+ // TODO(adonovan): show analysis status at per-file granularity.
+ fmt.Fprintf(&buf, "[%s]
", htmlpkg.EscapeString(status))
}
buf.WriteString("")
- formatGoSource(&buf, src, links, h, s)
+ formatGoSource(&buf, src, fi.Links, h, s)
buf.WriteString("
")
} else {
buf.WriteString("")
diff --git a/godoc/static/analysis/help.html b/godoc/static/analysis/help.html
index 30ed4a6ebd..61f0665145 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.
- Type analysis is relatively quick, requiring just a few seconds for
+ Type analysis is relatively quick, requiring about 10 seconds for
the >200 packages of the standard library, for example.
@@ -101,7 +101,7 @@
Pointer analysis features
- godoc -analysis=pointer
performs a precise
+ godoc -analysis=pointer
additionally performs a precise
whole-program pointer analysis. In other words, it
approximates the set of memory locations to which each
reference—not just vars of kind *T
, but also
@@ -113,10 +113,8 @@
channel.
- ⚠ 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.
Call graph navigation
@@ -250,9 +248,6 @@
Known issues
- ⚠ There is no UI indication of the state of
- the analysis (pending, complete, failed) during warm-up.
-
⚠ 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 6b288833bc..625ac16ea4 100644
--- a/godoc/static/static.go
+++ b/godoc/static/static.go
@@ -60,7 +60,7 @@ var Files = map[string]string{
expression and the method set of each type, and determines which
types are assignable to each interface type.
- Type analysis is relatively quick, requiring just a few seconds for
+ Type analysis is relatively quick, requiring about 10 seconds for
the >200 packages of the standard library, for example.
@@ -122,7 +122,7 @@ var Files = map[string]string{
Pointer analysis features
- godoc -analysis=pointer
performs a precise
+ godoc -analysis=pointer
additionally performs a precise
whole-program pointer analysis. In other words, it
approximates the set of memory locations to which each
reference—not just vars of kind *T
, but also
@@ -134,10 +134,8 @@ var Files = map[string]string{
channel.
- ⚠ 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.
Call graph navigation
@@ -271,9 +269,6 @@ var Files = map[string]string{
Known issues
- ⚠ There is no UI indication of the state of
- the analysis (pending, complete, failed) during warm-up.
-
⚠ 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