summaryrefslogtreecommitdiff
path: root/src/cmd/pprof/internal/report/source.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/pprof/internal/report/source.go')
-rw-r--r--src/cmd/pprof/internal/report/source.go450
1 files changed, 450 insertions, 0 deletions
diff --git a/src/cmd/pprof/internal/report/source.go b/src/cmd/pprof/internal/report/source.go
new file mode 100644
index 000000000..57300dd91
--- /dev/null
+++ b/src/cmd/pprof/internal/report/source.go
@@ -0,0 +1,450 @@
+// Copyright 2014 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 report
+
+// This file contains routines related to the generation of annotated
+// source listings.
+
+import (
+ "bufio"
+ "fmt"
+ "html/template"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+
+ "cmd/pprof/internal/plugin"
+)
+
+// printSource prints an annotated source listing, include all
+// functions with samples that match the regexp rpt.options.symbol.
+// The sources are sorted by function name and then by filename to
+// eliminate potential nondeterminism.
+func printSource(w io.Writer, rpt *Report) error {
+ o := rpt.options
+ g, err := newGraph(rpt)
+ if err != nil {
+ return err
+ }
+
+ // Identify all the functions that match the regexp provided.
+ // Group nodes for each matching function.
+ var functions nodes
+ functionNodes := make(map[string]nodes)
+ for _, n := range g.ns {
+ if !o.Symbol.MatchString(n.info.name) {
+ continue
+ }
+ if functionNodes[n.info.name] == nil {
+ functions = append(functions, n)
+ }
+ functionNodes[n.info.name] = append(functionNodes[n.info.name], n)
+ }
+ functions.sort(nameOrder)
+
+ fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
+ for _, fn := range functions {
+ name := fn.info.name
+
+ // Identify all the source files associated to this function.
+ // Group nodes for each source file.
+ var sourceFiles nodes
+ fileNodes := make(map[string]nodes)
+ for _, n := range functionNodes[name] {
+ if n.info.file == "" {
+ continue
+ }
+ if fileNodes[n.info.file] == nil {
+ sourceFiles = append(sourceFiles, n)
+ }
+ fileNodes[n.info.file] = append(fileNodes[n.info.file], n)
+ }
+
+ if len(sourceFiles) == 0 {
+ fmt.Printf("No source information for %s\n", name)
+ continue
+ }
+
+ sourceFiles.sort(fileOrder)
+
+ // Print each file associated with this function.
+ for _, fl := range sourceFiles {
+ filename := fl.info.file
+ fns := fileNodes[filename]
+ flatSum, cumSum := sumNodes(fns)
+
+ fnodes, path, err := getFunctionSource(name, filename, fns, 0, 0)
+ fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, path)
+ fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
+ rpt.formatValue(flatSum), rpt.formatValue(cumSum),
+ percentage(cumSum, rpt.total))
+
+ if err != nil {
+ fmt.Fprintf(w, " Error: %v\n", err)
+ continue
+ }
+
+ for _, fn := range fnodes {
+ fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt), fn.info.lineno, fn.info.name)
+ }
+ }
+ }
+ return nil
+}
+
+// printWebSource prints an annotated source listing, include all
+// functions with samples that match the regexp rpt.options.symbol.
+func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
+ o := rpt.options
+ g, err := newGraph(rpt)
+ if err != nil {
+ return err
+ }
+
+ // If the regexp source can be parsed as an address, also match
+ // functions that land on that address.
+ var address *uint64
+ if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
+ address = &hex
+ }
+
+ // Extract interesting symbols from binary files in the profile and
+ // classify samples per symbol.
+ symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj)
+ symNodes := nodesPerSymbol(g.ns, symbols)
+
+ // Sort symbols for printing.
+ var syms objSymbols
+ for s := range symNodes {
+ syms = append(syms, s)
+ }
+ sort.Sort(syms)
+
+ if len(syms) == 0 {
+ return fmt.Errorf("no samples found on routines matching: %s", o.Symbol.String())
+ }
+
+ printHeader(w, rpt)
+ for _, s := range syms {
+ name := s.sym.Name[0]
+ // Identify sources associated to a symbol by examining
+ // symbol samples. Classify samples per source file.
+ var sourceFiles nodes
+ fileNodes := make(map[string]nodes)
+ for _, n := range symNodes[s] {
+ if n.info.file == "" {
+ continue
+ }
+ if fileNodes[n.info.file] == nil {
+ sourceFiles = append(sourceFiles, n)
+ }
+ fileNodes[n.info.file] = append(fileNodes[n.info.file], n)
+ }
+
+ if len(sourceFiles) == 0 {
+ fmt.Printf("No source information for %s\n", name)
+ continue
+ }
+
+ sourceFiles.sort(fileOrder)
+
+ // Print each file associated with this function.
+ for _, fl := range sourceFiles {
+ filename := fl.info.file
+ fns := fileNodes[filename]
+
+ asm := assemblyPerSourceLine(symbols, fns, filename, obj)
+ start, end := sourceCoordinates(asm)
+
+ fnodes, path, err := getFunctionSource(name, filename, fns, start, end)
+ if err != nil {
+ fnodes, path = getMissingFunctionSource(filename, asm, start, end)
+ }
+
+ flatSum, cumSum := sumNodes(fnodes)
+ printFunctionHeader(w, name, path, flatSum, cumSum, rpt)
+ for _, fn := range fnodes {
+ printFunctionSourceLine(w, fn, asm[fn.info.lineno], rpt)
+ }
+ printFunctionClosing(w)
+ }
+ }
+ printPageClosing(w)
+ return nil
+}
+
+// sourceCoordinates returns the lowest and highest line numbers from
+// a set of assembly statements.
+func sourceCoordinates(asm map[int]nodes) (start, end int) {
+ for l := range asm {
+ if start == 0 || l < start {
+ start = l
+ }
+ if end == 0 || l > end {
+ end = l
+ }
+ }
+ return start, end
+}
+
+// assemblyPerSourceLine disassembles the binary containing a symbol
+// and classifies the assembly instructions according to its
+// corresponding source line, annotating them with a set of samples.
+func assemblyPerSourceLine(objSyms []*objSymbol, rs nodes, src string, obj plugin.ObjTool) map[int]nodes {
+ assembly := make(map[int]nodes)
+ // Identify symbol to use for this collection of samples.
+ o := findMatchingSymbol(objSyms, rs)
+ if o == nil {
+ return assembly
+ }
+
+ // Extract assembly for matched symbol
+ insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End)
+ if err != nil {
+ return assembly
+ }
+
+ srcBase := filepath.Base(src)
+ anodes := annotateAssembly(insns, rs, o.base)
+ var lineno = 0
+ for _, an := range anodes {
+ if filepath.Base(an.info.file) == srcBase {
+ lineno = an.info.lineno
+ }
+ if lineno != 0 {
+ assembly[lineno] = append(assembly[lineno], an)
+ }
+ }
+
+ return assembly
+}
+
+// findMatchingSymbol looks for the symbol that corresponds to a set
+// of samples, by comparing their addresses.
+func findMatchingSymbol(objSyms []*objSymbol, ns nodes) *objSymbol {
+ for _, n := range ns {
+ for _, o := range objSyms {
+ if filepath.Base(o.sym.File) == n.info.objfile &&
+ o.sym.Start <= n.info.address-o.base &&
+ n.info.address-o.base <= o.sym.End {
+ return o
+ }
+ }
+ }
+ return nil
+}
+
+// printHeader prints the page header for a weblist report.
+func printHeader(w io.Writer, rpt *Report) {
+ fmt.Fprintln(w, weblistPageHeader)
+
+ var labels []string
+ for _, l := range legendLabels(rpt) {
+ labels = append(labels, template.HTMLEscapeString(l))
+ }
+
+ fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
+ strings.Join(labels, "<br>\n"),
+ rpt.formatValue(rpt.total),
+ )
+}
+
+// printFunctionHeader prints a function header for a weblist report.
+func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
+ fmt.Fprintf(w, `<h1>%s</h1>%s
+<pre onClick="pprof_toggle_asm()">
+ Total: %10s %10s (flat, cum) %s
+`,
+ template.HTMLEscapeString(name), template.HTMLEscapeString(path),
+ rpt.formatValue(flatSum), rpt.formatValue(cumSum),
+ percentage(cumSum, rpt.total))
+}
+
+// printFunctionSourceLine prints a source line and the corresponding assembly.
+func printFunctionSourceLine(w io.Writer, fn *node, assembly nodes, rpt *Report) {
+ if len(assembly) == 0 {
+ fmt.Fprintf(w,
+ "<span class=line> %6d</span> <span class=nop> %10s %10s %s </span>\n",
+ fn.info.lineno,
+ valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
+ template.HTMLEscapeString(fn.info.name))
+ return
+ }
+
+ fmt.Fprintf(w,
+ "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %s </span>",
+ fn.info.lineno,
+ valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt),
+ template.HTMLEscapeString(fn.info.name))
+ fmt.Fprint(w, "<span class=asm>")
+ for _, an := range assembly {
+ var fileline string
+ class := "disasmloc"
+ if an.info.file != "" {
+ fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.info.file), an.info.lineno)
+ if an.info.lineno != fn.info.lineno {
+ class = "unimportant"
+ }
+ }
+ fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "",
+ valueOrDot(an.flat, rpt), valueOrDot(an.cum, rpt),
+ an.info.address,
+ template.HTMLEscapeString(an.info.name),
+ class,
+ template.HTMLEscapeString(fileline))
+ }
+ fmt.Fprintln(w, "</span>")
+}
+
+// printFunctionClosing prints the end of a function in a weblist report.
+func printFunctionClosing(w io.Writer) {
+ fmt.Fprintln(w, "</pre>")
+}
+
+// printPageClosing prints the end of the page in a weblist report.
+func printPageClosing(w io.Writer) {
+ fmt.Fprintln(w, weblistPageClosing)
+}
+
+// getFunctionSource collects the sources of a function from a source
+// file and annotates it with the samples in fns. Returns the sources
+// as nodes, using the info.name field to hold the source code.
+func getFunctionSource(fun, file string, fns nodes, start, end int) (nodes, string, error) {
+ f, file, err := adjustSourcePath(file)
+ if err != nil {
+ return nil, file, err
+ }
+
+ lineNodes := make(map[int]nodes)
+
+ // Collect source coordinates from profile.
+ const margin = 5 // Lines before first/after last sample.
+ if start == 0 {
+ if fns[0].info.startLine != 0 {
+ start = fns[0].info.startLine
+ } else {
+ start = fns[0].info.lineno - margin
+ }
+ } else {
+ start -= margin
+ }
+ if end == 0 {
+ end = fns[0].info.lineno
+ }
+ end += margin
+ for _, n := range fns {
+ lineno := n.info.lineno
+ nodeStart := n.info.startLine
+ if nodeStart == 0 {
+ nodeStart = lineno - margin
+ }
+ nodeEnd := lineno + margin
+ if nodeStart < start {
+ start = nodeStart
+ } else if nodeEnd > end {
+ end = nodeEnd
+ }
+ lineNodes[lineno] = append(lineNodes[lineno], n)
+ }
+
+ var src nodes
+ buf := bufio.NewReader(f)
+ lineno := 1
+ for {
+ line, err := buf.ReadString('\n')
+ if err != nil {
+ if line == "" || err != io.EOF {
+ return nil, file, err
+ }
+ }
+ if lineno >= start {
+ flat, cum := sumNodes(lineNodes[lineno])
+
+ src = append(src, &node{
+ info: nodeInfo{
+ name: strings.TrimRight(line, "\n"),
+ lineno: lineno,
+ },
+ flat: flat,
+ cum: cum,
+ })
+ }
+ lineno++
+ if lineno > end {
+ break
+ }
+ }
+ return src, file, nil
+}
+
+// getMissingFunctionSource creates a dummy function body to point to
+// the source file and annotates it with the samples in asm.
+func getMissingFunctionSource(filename string, asm map[int]nodes, start, end int) (nodes, string) {
+ var fnodes nodes
+ for i := start; i <= end; i++ {
+ lrs := asm[i]
+ if len(lrs) == 0 {
+ continue
+ }
+ flat, cum := sumNodes(lrs)
+ fnodes = append(fnodes, &node{
+ info: nodeInfo{
+ name: "???",
+ lineno: i,
+ },
+ flat: flat,
+ cum: cum,
+ })
+ }
+ return fnodes, filename
+}
+
+// adjustSourcePath adjusts the pathe for a source file by trimmming
+// known prefixes and searching for the file on all parents of the
+// current working dir.
+func adjustSourcePath(path string) (*os.File, string, error) {
+ path = trimPath(path)
+ f, err := os.Open(path)
+ if err == nil {
+ return f, path, nil
+ }
+
+ if dir, wderr := os.Getwd(); wderr == nil {
+ for {
+ parent := filepath.Dir(dir)
+ if parent == dir {
+ break
+ }
+ if f, err := os.Open(filepath.Join(parent, path)); err == nil {
+ return f, filepath.Join(parent, path), nil
+ }
+
+ dir = parent
+ }
+ }
+
+ return nil, path, err
+}
+
+// trimPath cleans up a path by removing prefixes that are commonly
+// found on profiles.
+func trimPath(path string) string {
+ basePaths := []string{
+ "/proc/self/cwd/./",
+ "/proc/self/cwd/",
+ }
+
+ sPath := filepath.ToSlash(path)
+
+ for _, base := range basePaths {
+ if strings.HasPrefix(sPath, base) {
+ return filepath.FromSlash(sPath[len(base):])
+ }
+ }
+ return path
+}