1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:14:46 -07:00

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
This commit is contained in:
Alan Donovan 2014-07-09 07:59:55 -04:00
parent bb2f616e98
commit 99d45c0e8e
5 changed files with 82 additions and 69 deletions

View File

@ -59,7 +59,6 @@ Type info:
- Suppress toggle divs for empty method sets. - Suppress toggle divs for empty method sets.
Misc: 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. - The [X] button in the lower pane is subject to scrolling.
- Should the lower pane be floating? An iframe? - Should the lower pane be floating? An iframe?
When we change document.location by clicking on a link, it will go away. 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 filenames). 20% of the HTML is L%d spans (now disabled). The HTML
also contains lots of tooltips for long struct/interface types. also contains lots of tooltips for long struct/interface types.
De-dup or just abbreviate? The actual formatting is very fast. 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.

View File

@ -123,6 +123,13 @@ func (e errorLink) Write(w io.Writer, _ int, start bool) {
// -- fileInfo --------------------------------------------------------- // -- 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 // A fileInfo is the server's store of hyperlinks and JSON data for a
// particular file. // particular file.
type fileInfo struct { type fileInfo struct {
@ -154,20 +161,28 @@ func (fi *fileInfo) addData(x interface{}) int {
return index return index
} }
// get returns new slices containing opaque JSON values and the HTML link markup for fi. // get returns the file info in external form.
// Callers must not mutate the elements. // Callers must not mutate its fields.
func (fi *fileInfo) get() (data []interface{}, links []Link) { func (fi *fileInfo) get() FileInfo {
var r FileInfo
// Copy slices, to avoid races. // Copy slices, to avoid races.
fi.mu.Lock() fi.mu.Lock()
data = append(data, fi.data...) r.Data = append(r.Data, fi.data...)
if !fi.sorted { if !fi.sorted {
sort.Sort(linksByStart(fi.links)) sort.Sort(linksByStart(fi.links))
fi.sorted = true fi.sorted = true
} }
links = append(links, fi.links...) r.Links = append(r.Links, fi.links...)
fi.mu.Unlock() 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 { type pkgInfo struct {
@ -190,16 +205,17 @@ func (pi *pkgInfo) addType(t *TypeInfoJSON) {
pi.mu.Unlock() pi.mu.Unlock()
} }
// get returns new slices of JSON values for the callgraph and type info for pi. // get returns the package info in external form.
// Callers must not mutate the slice elements or the map. // Callers must not mutate its fields.
func (pi *pkgInfo) get() (callGraph []*PCGNodeJSON, callGraphIndex map[string]int, types []*TypeInfoJSON) { func (pi *pkgInfo) get() PackageInfo {
var r PackageInfo
// Copy slices, to avoid races. // Copy slices, to avoid races.
pi.mu.Lock() pi.mu.Lock()
callGraph = append(callGraph, pi.callGraph...) r.CallGraph = append(r.CallGraph, pi.callGraph...)
callGraphIndex = pi.callGraphIndex r.CallGraphIndex = pi.callGraphIndex
types = append(types, pi.types...) r.Types = append(r.Types, pi.types...)
pi.mu.Unlock() pi.mu.Unlock()
return return r
} }
// -- Result ----------------------------------------------------------- // -- Result -----------------------------------------------------------
@ -209,6 +225,7 @@ func (pi *pkgInfo) get() (callGraph []*PCGNodeJSON, callGraphIndex map[string]in
// and JavaScript data referenced by the links. // and JavaScript data referenced by the links.
type Result struct { type Result struct {
mu sync.Mutex // guards maps (but not their contents) mu sync.Mutex // guards maps (but not their contents)
status string // global analysis status
fileInfos map[string]*fileInfo // keys are godoc file URLs fileInfos map[string]*fileInfo // keys are godoc file URLs
pkgInfos map[string]*pkgInfo // keys are import paths pkgInfos map[string]*pkgInfo // keys are import paths
} }
@ -229,12 +246,26 @@ func (res *Result) fileInfo(url string) *fileInfo {
return fi 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 // FileInfo returns new slices containing opaque JSON values and the
// HTML link markup for the specified godoc file URL. Thread-safe. // HTML link markup for the specified godoc file URL. Thread-safe.
// Callers must not mutate the elements. // Callers must not mutate the elements.
// It returns "zero" if no data is available. // 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() 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 // PackageInfo returns new slices of JSON values for the callgraph and
// type info for the specified package. Thread-safe. // 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. // 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() return res.pkgInfo(importPath).get()
} }
@ -333,17 +364,18 @@ func Run(pta bool, result *Result) {
//args = []string{"fmt"} //args = []string{"fmt"}
if _, err := conf.FromArgs(args, true); err != nil { 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 return
} }
log.Print("Loading and type-checking packages...") result.setStatusf("Loading and type-checking packages...")
iprog, err := conf.Load() iprog, err := conf.Load()
if iprog != nil { if iprog != nil {
log.Printf("Loaded %d packages.", len(iprog.AllPackages)) log.Printf("Loaded %d packages.", len(iprog.AllPackages))
} }
if err != nil { if err != nil {
log.Printf("Analysis failed: %s\n", err) result.setStatusf("Loading failed: %s.\n", err)
return return
} }
@ -365,9 +397,9 @@ func Run(pta bool, result *Result) {
log.Print("Main packages: ", mainPkgs) log.Print("Main packages: ", mainPkgs)
// Build SSA code for bodies of all functions in the whole program. // Build SSA code for bodies of all functions in the whole program.
log.Print("Building SSA...") result.setStatusf("Constructing SSA form...")
prog.BuildAll() prog.BuildAll()
log.Print("SSA building complete") log.Print("SSA construction complete")
a := analysis{ a := analysis{
result: result, 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) facts := computeImplements(&a.prog.MethodSets, a.allNamed)
// Add the type-based analysis results. // Add the type-based analysis results.
log.Print("Extracting type info...") log.Print("Extracting type info...")
for _, info := range iprog.AllPackages { for _, info := range iprog.AllPackages {
a.doTypeInfo(info, facts) a.doTypeInfo(info, facts)
} }
a.visitInstrs(pta) a.visitInstrs(pta)
log.Print("Extracting type info complete") result.setStatusf("Type analysis complete.")
if pta { if pta {
a.pointer(mainPkgs) a.pointer(mainPkgs)
@ -514,23 +545,24 @@ func (a *analysis) pointer(mainPkgs []*ssa.Package) {
a.ptaConfig.BuildCallGraph = true a.ptaConfig.BuildCallGraph = true
a.ptaConfig.Reflection = false // (for now) a.ptaConfig.Reflection = false // (for now)
log.Print("Running pointer analysis...") a.result.setStatusf("Pointer analysis running...")
ptares, err := pointer.Analyze(&a.ptaConfig) ptares, err := pointer.Analyze(&a.ptaConfig)
if err != nil { if err != nil {
// If this happens, it indicates a bug. // If this happens, it indicates a bug.
log.Printf("Pointer analysis failed: %s", err) a.result.setStatusf("Pointer analysis failed: %s.", err)
return return
} }
log.Print("Pointer analysis complete.") log.Print("Pointer analysis complete.")
// Add the results of pointer analysis. // Add the results of pointer analysis.
log.Print("Computing channel peers...") a.result.setStatusf("Computing channel peers...")
a.doChannelPeers(ptares.Queries) a.doChannelPeers(ptares.Queries)
log.Print("Computing dynamic call graph edges...") a.result.setStatusf("Computing dynamic call graph edges...")
a.doCallgraph(ptares.CallGraph) a.doCallgraph(ptares.CallGraph)
log.Print("Done") a.result.setStatusf("Analysis complete.")
} }
type linksByStart []Link type linksByStart []Link

View File

@ -260,14 +260,13 @@ func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
// Emit JSON array for type information. // Emit JSON array for type information.
// TODO(adonovan): issue a "pending..." message if results not ready. // TODO(adonovan): display the h.c.Analysis.Status() message in the UI.
var callGraph []*analysis.PCGNodeJSON pi := h.c.Analysis.PackageInfo(relpath)
var typeInfos []*analysis.TypeInfoJSON info.CallGraphIndex = pi.CallGraphIndex
callGraph, info.CallGraphIndex, typeInfos = h.c.Analysis.PackageInfo(relpath) info.CallGraph = htmltemplate.JS(marshalJSON(pi.CallGraph))
info.CallGraph = htmltemplate.JS(marshalJSON(callGraph)) info.AnalysisData = htmltemplate.JS(marshalJSON(pi.Types))
info.AnalysisData = htmltemplate.JS(marshalJSON(typeInfos))
info.TypeInfoIndex = make(map[string]int) info.TypeInfoIndex = make(map[string]int)
for i, ti := range typeInfos { for i, ti := range pi.Types {
info.TypeInfoIndex[ti.Name] = i info.TypeInfoIndex[ti.Name] = i
} }
@ -501,20 +500,19 @@ func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abs
var buf bytes.Buffer var buf bytes.Buffer
if pathpkg.Ext(abspath) == ".go" { if pathpkg.Ext(abspath) == ".go" {
// Find markup links for this file (e.g. "/src/pkg/fmt/print.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.WriteString("<script type='text/javascript'>document.ANALYSIS_DATA = ")
buf.Write(marshalJSON(data)) buf.Write(marshalJSON(fi.Data))
buf.WriteString(";</script>\n") buf.WriteString(";</script>\n")
// TODO(adonovan): indicate whether analysis is if status := p.Corpus.Analysis.Status(); status != "" {
// disabled, pending, completed or failed. buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a> ")
// For now, display help link only if 'completed'. // TODO(adonovan): show analysis status at per-file granularity.
if links != nil { fmt.Fprintf(&buf, "<span style='color: grey'>[%s]</span><br/>", htmlpkg.EscapeString(status))
buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a><br/>")
} }
buf.WriteString("<pre>") buf.WriteString("<pre>")
formatGoSource(&buf, src, links, h, s) formatGoSource(&buf, src, fi.Links, h, s)
buf.WriteString("</pre>") buf.WriteString("</pre>")
} else { } else {
buf.WriteString("<pre>") buf.WriteString("<pre>")

View File

@ -39,7 +39,7 @@
expression and the method set of each type, and determines which expression and the method set of each type, and determines which
types are assignable to each interface type. 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. the &gt;200 packages of the standard library, for example.
</p> </p>
@ -101,7 +101,7 @@
<h2>Pointer analysis features</h2> <h2>Pointer analysis features</h2>
<p> <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 whole-program <b>pointer analysis</b>. In other words, it
approximates the set of memory locations to which each approximates the set of memory locations to which each
reference&mdash;not just vars of kind <code>*T</code>, but also reference&mdash;not just vars of kind <code>*T</code>, but also
@ -113,10 +113,8 @@
channel. channel.
</p> </p>
<p> <p>
<span class='err'></span> Pointer analysis is currently quite slow, Pointer analysis is slower than type analysis, taking an additional
taking around two minutes for the standard library. This will 15 seconds or so for the standard libraries and their tests.
improve markedly with the planned addition of a constraint
optimizer.
</p> </p>
<h3>Call graph navigation</h3> <h3>Call graph navigation</h3>
@ -250,9 +248,6 @@
<h2>Known issues</h2> <h2>Known issues</h2>
<p> <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 <span class='err'></span> All analysis results pertain to exactly
one configuration (e.g. amd64 linux). Files that are conditionally one configuration (e.g. amd64 linux). Files that are conditionally
compiled based on different platforms or build tags are not visible compiled based on different platforms or build tags are not visible

View File

@ -60,7 +60,7 @@ var Files = map[string]string{
expression and the method set of each type, and determines which expression and the method set of each type, and determines which
types are assignable to each interface type. 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. the >200 packages of the standard library, for example.
</p> </p>
@ -122,7 +122,7 @@ var Files = map[string]string{
<h2>Pointer analysis features</h2> <h2>Pointer analysis features</h2>
<p> <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 whole-program <b>pointer analysis</b>. In other words, it
approximates the set of memory locations to which each approximates the set of memory locations to which each
reference&mdash;not just vars of kind <code>*T</code>, but also reference&mdash;not just vars of kind <code>*T</code>, but also
@ -134,10 +134,8 @@ var Files = map[string]string{
channel. channel.
</p> </p>
<p> <p>
<span class='err'></span> Pointer analysis is currently quite slow, Pointer analysis is slower than type analysis, taking an additional
taking around two minutes for the standard library. This will 15 seconds or so for the standard libraries and their tests.
improve markedly with the planned addition of a constraint
optimizer.
</p> </p>
<h3>Call graph navigation</h3> <h3>Call graph navigation</h3>
@ -271,9 +269,6 @@ var Files = map[string]string{
<h2>Known issues</h2> <h2>Known issues</h2>
<p> <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 <span class='err'></span> All analysis results pertain to exactly
one configuration (e.g. amd64 linux). Files that are conditionally one configuration (e.g. amd64 linux). Files that are conditionally
compiled based on different platforms or build tags are not visible compiled based on different platforms or build tags are not visible