123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- // Copyright 2014 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package report
-
- // This file contains routines related to the generation of annotated
- // source listings.
-
- import (
- "bufio"
- "fmt"
- "html/template"
- "io"
- "os"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/google/pprof/internal/graph"
- "github.com/google/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 := rpt.newGraph(nil)
-
- // Identify all the functions that match the regexp provided.
- // Group nodes for each matching function.
- var functions graph.Nodes
- functionNodes := make(map[string]graph.Nodes)
- for _, n := range g.Nodes {
- 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(graph.NameOrder)
-
- sourcePath := o.SourcePath
- if sourcePath == "" {
- wd, err := os.Getwd()
- if err != nil {
- return fmt.Errorf("Could not stat current dir: %v", err)
- }
- sourcePath = wd
- }
-
- 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 graph.Nodes
- fileNodes := make(map[string]graph.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.Fprintf(w, "No source information for %s\n", name)
- continue
- }
-
- sourceFiles.Sort(graph.FileOrder)
-
- // Print each file associated with this function.
- for _, fl := range sourceFiles {
- filename := fl.Info.File
- fns := fileNodes[filename]
- flatSum, cumSum := fns.Sum()
-
- fnodes, _, err := getSourceFromFile(filename, sourcePath, fns, 0, 0)
- fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
- 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 := rpt.newGraph(nil)
-
- // 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
- }
-
- sourcePath := o.SourcePath
- if sourcePath == "" {
- wd, err := os.Getwd()
- if err != nil {
- return fmt.Errorf("Could not stat current dir: %v", err)
- }
- sourcePath = wd
- }
-
- type fileFunction struct {
- fileName, functionName string
- }
-
- // 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.Nodes, symbols)
-
- // Identify sources associated to a symbol by examining
- // symbol samples. Classify samples per source file.
- fileNodes := make(map[fileFunction]graph.Nodes)
- if len(symNodes) == 0 {
- for _, n := range g.Nodes {
- if n.Info.File == "" || !o.Symbol.MatchString(n.Info.Name) {
- continue
- }
- ff := fileFunction{n.Info.File, n.Info.Name}
- fileNodes[ff] = append(fileNodes[ff], n)
- }
- } else {
- for _, nodes := range symNodes {
- for _, n := range nodes {
- if n.Info.File != "" {
- ff := fileFunction{n.Info.File, n.Info.Name}
- fileNodes[ff] = append(fileNodes[ff], n)
- }
- }
- }
- }
-
- if len(fileNodes) == 0 {
- return fmt.Errorf("No source information for %s\n", o.Symbol.String())
- }
-
- sourceFiles := make(graph.Nodes, 0, len(fileNodes))
- for _, nodes := range fileNodes {
- sNode := *nodes[0]
- sNode.Flat, sNode.Cum = nodes.Sum()
- sourceFiles = append(sourceFiles, &sNode)
- }
- sourceFiles.Sort(graph.FileOrder)
-
- // Print each file associated with this function.
- printHeader(w, rpt)
- for _, n := range sourceFiles {
- ff := fileFunction{n.Info.File, n.Info.Name}
- fns := fileNodes[ff]
-
- asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj)
- start, end := sourceCoordinates(asm)
-
- fnodes, path, err := getSourceFromFile(ff.fileName, sourcePath, fns, start, end)
- if err != nil {
- fnodes, path = getMissingFunctionSource(ff.fileName, asm, start, end)
- }
-
- printFunctionHeader(w, ff.functionName, path, n.Flat, n.Cum, 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][]assemblyInstruction) (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 graph.Nodes, src string, obj plugin.ObjTool) map[int][]assemblyInstruction {
- assembly := make(map[int][]assemblyInstruction)
- // Identify symbol to use for this collection of samples.
- o := findMatchingSymbol(objSyms, rs)
- if o == nil {
- return assembly
- }
-
- // Extract assembly for matched symbol
- insts, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End)
- if err != nil {
- return assembly
- }
-
- srcBase := filepath.Base(src)
- anodes := annotateAssembly(insts, rs, o.base)
- var lineno = 0
- for _, an := range anodes {
- if filepath.Base(an.file) == srcBase {
- lineno = an.line
- }
- 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 graph.Nodes) *objSymbol {
- for _, n := range ns {
- for _, o := range objSyms {
- if filepath.Base(o.sym.File) == filepath.Base(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 ProfileLabels(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(event)">
- 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 *graph.Node, assembly []assemblyInstruction, 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.file != "" {
- fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.file), an.line)
- if an.line != fn.Info.Lineno {
- class = "unimportant"
- }
- }
- flat, cum := an.flat, an.cum
- if an.flatDiv != 0 {
- flat = flat / an.flatDiv
- }
- if an.cumDiv != 0 {
- cum = cum / an.cumDiv
- }
- fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "",
- valueOrDot(flat, rpt), valueOrDot(cum, rpt),
- an.address,
- template.HTMLEscapeString(an.instruction),
- 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)
- }
-
- // getSourceFromFile 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 getSourceFromFile(file, sourcePath string, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
- file = trimPath(file)
- f, err := openSourceFile(file, sourcePath)
- if err != nil {
- return nil, file, err
- }
-
- lineNodes := make(map[int]graph.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 graph.Nodes
- buf := bufio.NewReader(f)
- lineno := 1
- for {
- line, err := buf.ReadString('\n')
- if err != nil {
- if err != io.EOF {
- return nil, file, err
- }
- if line == "" {
- break
- }
- }
- if lineno >= start {
- flat, cum := lineNodes[lineno].Sum()
-
- src = append(src, &graph.Node{
- Info: graph.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][]assemblyInstruction, start, end int) (graph.Nodes, string) {
- var fnodes graph.Nodes
- for i := start; i <= end; i++ {
- insts := asm[i]
- if len(insts) == 0 {
- continue
- }
- var group assemblyInstruction
- for _, insn := range insts {
- group.flat += insn.flat
- group.cum += insn.cum
- group.flatDiv += insn.flatDiv
- group.cumDiv += insn.cumDiv
- }
- flat := group.flatValue()
- cum := group.cumValue()
- fnodes = append(fnodes, &graph.Node{
- Info: graph.NodeInfo{
- Name: "???",
- Lineno: i,
- },
- Flat: flat,
- Cum: cum,
- })
- }
- return fnodes, filename
- }
-
- // openSourceFile opens a source file from a name encoded in a
- // profile. File names in a profile after often relative paths, so
- // search them in each of the paths in searchPath (or CWD by default),
- // and their parents.
- func openSourceFile(path, searchPath string) (*os.File, error) {
- if filepath.IsAbs(path) {
- f, err := os.Open(path)
- return f, err
- }
-
- // Scan each component of the path
- for _, dir := range strings.Split(searchPath, ":") {
- // Search up for every parent of each possible path.
- for {
- filename := filepath.Join(dir, path)
- if f, err := os.Open(filename); err == nil {
- return f, nil
- }
- parent := filepath.Dir(dir)
- if parent == dir {
- break
- }
- dir = parent
- }
- }
-
- return nil, fmt.Errorf("Could not find file %s on path %s", path, searchPath)
- }
-
- // 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
- }
|