Source file src/cmd/vendor/github.com/google/pprof/internal/report/source.go

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package report
    16  
    17  // This file contains routines related to the generation of annotated
    18  // source listings.
    19  
    20  import (
    21  	"bufio"
    22  	"fmt"
    23  	"html/template"
    24  	"io"
    25  	"os"
    26  	"path/filepath"
    27  	"regexp"
    28  	"sort"
    29  	"strconv"
    30  	"strings"
    31  
    32  	"github.com/google/pprof/internal/graph"
    33  	"github.com/google/pprof/internal/measurement"
    34  	"github.com/google/pprof/internal/plugin"
    35  	"github.com/google/pprof/profile"
    36  )
    37  
    38  // printSource prints an annotated source listing, include all
    39  // functions with samples that match the regexp rpt.options.symbol.
    40  // The sources are sorted by function name and then by filename to
    41  // eliminate potential nondeterminism.
    42  func printSource(w io.Writer, rpt *Report) error {
    43  	o := rpt.options
    44  	g := rpt.newGraph(nil)
    45  
    46  	// Identify all the functions that match the regexp provided.
    47  	// Group nodes for each matching function.
    48  	var functions graph.Nodes
    49  	functionNodes := make(map[string]graph.Nodes)
    50  	for _, n := range g.Nodes {
    51  		if !o.Symbol.MatchString(n.Info.Name) {
    52  			continue
    53  		}
    54  		if functionNodes[n.Info.Name] == nil {
    55  			functions = append(functions, n)
    56  		}
    57  		functionNodes[n.Info.Name] = append(functionNodes[n.Info.Name], n)
    58  	}
    59  	functions.Sort(graph.NameOrder)
    60  
    61  	if len(functionNodes) == 0 {
    62  		return fmt.Errorf("no matches found for regexp: %s", o.Symbol)
    63  	}
    64  
    65  	sourcePath := o.SourcePath
    66  	if sourcePath == "" {
    67  		wd, err := os.Getwd()
    68  		if err != nil {
    69  			return fmt.Errorf("could not stat current dir: %v", err)
    70  		}
    71  		sourcePath = wd
    72  	}
    73  	reader := newSourceReader(sourcePath, o.TrimPath)
    74  
    75  	fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
    76  	for _, fn := range functions {
    77  		name := fn.Info.Name
    78  
    79  		// Identify all the source files associated to this function.
    80  		// Group nodes for each source file.
    81  		var sourceFiles graph.Nodes
    82  		fileNodes := make(map[string]graph.Nodes)
    83  		for _, n := range functionNodes[name] {
    84  			if n.Info.File == "" {
    85  				continue
    86  			}
    87  			if fileNodes[n.Info.File] == nil {
    88  				sourceFiles = append(sourceFiles, n)
    89  			}
    90  			fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
    91  		}
    92  
    93  		if len(sourceFiles) == 0 {
    94  			fmt.Fprintf(w, "No source information for %s\n", name)
    95  			continue
    96  		}
    97  
    98  		sourceFiles.Sort(graph.FileOrder)
    99  
   100  		// Print each file associated with this function.
   101  		for _, fl := range sourceFiles {
   102  			filename := fl.Info.File
   103  			fns := fileNodes[filename]
   104  			flatSum, cumSum := fns.Sum()
   105  
   106  			fnodes, _, err := getSourceFromFile(filename, reader, fns, 0, 0)
   107  			fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
   108  			fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
   109  				rpt.formatValue(flatSum), rpt.formatValue(cumSum),
   110  				measurement.Percentage(cumSum, rpt.total))
   111  
   112  			if err != nil {
   113  				fmt.Fprintf(w, " Error: %v\n", err)
   114  				continue
   115  			}
   116  
   117  			for _, fn := range fnodes {
   118  				fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)
   119  			}
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  // printWebSource prints an annotated source listing, include all
   126  // functions with samples that match the regexp rpt.options.symbol.
   127  func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
   128  	printHeader(w, rpt)
   129  	if err := PrintWebList(w, rpt, obj, -1); err != nil {
   130  		return err
   131  	}
   132  	printPageClosing(w)
   133  	return nil
   134  }
   135  
   136  // sourcePrinter holds state needed for generating source+asm HTML listing.
   137  type sourcePrinter struct {
   138  	reader     *sourceReader
   139  	synth      *synthCode
   140  	objectTool plugin.ObjTool
   141  	objects    map[string]plugin.ObjFile  // Opened object files
   142  	sym        *regexp.Regexp             // May be nil
   143  	files      map[string]*sourceFile     // Set of files to print.
   144  	insts      map[uint64]instructionInfo // Instructions of interest (keyed by address).
   145  
   146  	// Set of function names that we are interested in (because they had
   147  	// a sample and match sym).
   148  	interest map[string]bool
   149  
   150  	// Mapping from system function names to printable names.
   151  	prettyNames map[string]string
   152  }
   153  
   154  // addrInfo holds information for an address we are interested in.
   155  type addrInfo struct {
   156  	loc *profile.Location // Always non-nil
   157  	obj plugin.ObjFile    // May be nil
   158  }
   159  
   160  // instructionInfo holds collected information for an instruction.
   161  type instructionInfo struct {
   162  	objAddr   uint64 // Address in object file (with base subtracted out)
   163  	length    int    // Instruction length in bytes
   164  	disasm    string // Disassembly of instruction
   165  	file      string // For top-level function in which instruction occurs
   166  	line      int    // For top-level function in which instruction occurs
   167  	flat, cum int64  // Samples to report (divisor already applied)
   168  }
   169  
   170  // sourceFile contains collected information for files we will print.
   171  type sourceFile struct {
   172  	fname    string
   173  	cum      int64
   174  	flat     int64
   175  	lines    map[int][]sourceInst // Instructions to show per line
   176  	funcName map[int]string       // Function name per line
   177  }
   178  
   179  // sourceInst holds information for an instruction to be displayed.
   180  type sourceInst struct {
   181  	addr  uint64
   182  	stack []callID // Inlined call-stack
   183  }
   184  
   185  // sourceFunction contains information for a contiguous range of lines per function we
   186  // will print.
   187  type sourceFunction struct {
   188  	name       string
   189  	begin, end int // Line numbers (end is not included in the range)
   190  	flat, cum  int64
   191  }
   192  
   193  // addressRange is a range of addresses plus the object file that contains it.
   194  type addressRange struct {
   195  	begin, end uint64
   196  	obj        plugin.ObjFile
   197  	mapping    *profile.Mapping
   198  	score      int64 // Used to order ranges for processing
   199  }
   200  
   201  // PrintWebList prints annotated source listing of rpt to w.
   202  // rpt.prof should contain inlined call info.
   203  func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) error {
   204  	sourcePath := rpt.options.SourcePath
   205  	if sourcePath == "" {
   206  		wd, err := os.Getwd()
   207  		if err != nil {
   208  			return fmt.Errorf("could not stat current dir: %v", err)
   209  		}
   210  		sourcePath = wd
   211  	}
   212  	sp := newSourcePrinter(rpt, obj, sourcePath)
   213  	if len(sp.interest) == 0 {
   214  		return fmt.Errorf("no matches found for regexp: %s", rpt.options.Symbol)
   215  	}
   216  	sp.print(w, maxFiles, rpt)
   217  	sp.close()
   218  	return nil
   219  }
   220  
   221  func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter {
   222  	sp := &sourcePrinter{
   223  		reader:      newSourceReader(sourcePath, rpt.options.TrimPath),
   224  		synth:       newSynthCode(rpt.prof.Mapping),
   225  		objectTool:  obj,
   226  		objects:     map[string]plugin.ObjFile{},
   227  		sym:         rpt.options.Symbol,
   228  		files:       map[string]*sourceFile{},
   229  		insts:       map[uint64]instructionInfo{},
   230  		prettyNames: map[string]string{},
   231  		interest:    map[string]bool{},
   232  	}
   233  
   234  	// If the regexp source can be parsed as an address, also match
   235  	// functions that land on that address.
   236  	var address *uint64
   237  	if sp.sym != nil {
   238  		if hex, err := strconv.ParseUint(sp.sym.String(), 0, 64); err == nil {
   239  			address = &hex
   240  		}
   241  	}
   242  
   243  	addrs := map[uint64]addrInfo{}
   244  	flat := map[uint64]int64{}
   245  	cum := map[uint64]int64{}
   246  
   247  	// Record an interest in the function corresponding to lines[index].
   248  	markInterest := func(addr uint64, loc *profile.Location, index int) {
   249  		fn := loc.Line[index]
   250  		if fn.Function == nil {
   251  			return
   252  		}
   253  		sp.interest[fn.Function.Name] = true
   254  		sp.interest[fn.Function.SystemName] = true
   255  		if _, ok := addrs[addr]; !ok {
   256  			addrs[addr] = addrInfo{loc, sp.objectFile(loc.Mapping)}
   257  		}
   258  	}
   259  
   260  	// See if sp.sym matches line.
   261  	matches := func(line profile.Line) bool {
   262  		if line.Function == nil {
   263  			return false
   264  		}
   265  		return sp.sym.MatchString(line.Function.Name) ||
   266  			sp.sym.MatchString(line.Function.SystemName) ||
   267  			sp.sym.MatchString(line.Function.Filename)
   268  	}
   269  
   270  	// Extract sample counts and compute set of interesting functions.
   271  	for _, sample := range rpt.prof.Sample {
   272  		value := rpt.options.SampleValue(sample.Value)
   273  		if rpt.options.SampleMeanDivisor != nil {
   274  			div := rpt.options.SampleMeanDivisor(sample.Value)
   275  			if div != 0 {
   276  				value /= div
   277  			}
   278  		}
   279  
   280  		// Find call-sites matching sym.
   281  		for i := len(sample.Location) - 1; i >= 0; i-- {
   282  			loc := sample.Location[i]
   283  			for _, line := range loc.Line {
   284  				if line.Function == nil {
   285  					continue
   286  				}
   287  				sp.prettyNames[line.Function.SystemName] = line.Function.Name
   288  			}
   289  
   290  			addr := loc.Address
   291  			if addr == 0 {
   292  				// Some profiles are missing valid addresses.
   293  				addr = sp.synth.address(loc)
   294  			}
   295  
   296  			cum[addr] += value
   297  			if i == 0 {
   298  				flat[addr] += value
   299  			}
   300  
   301  			if sp.sym == nil || (address != nil && addr == *address) {
   302  				// Interested in top-level entry of stack.
   303  				if len(loc.Line) > 0 {
   304  					markInterest(addr, loc, len(loc.Line)-1)
   305  				}
   306  				continue
   307  			}
   308  
   309  			// Search in inlined stack for a match.
   310  			matchFile := (loc.Mapping != nil && sp.sym.MatchString(loc.Mapping.File))
   311  			for j, line := range loc.Line {
   312  				if (j == 0 && matchFile) || matches(line) {
   313  					markInterest(addr, loc, j)
   314  				}
   315  			}
   316  		}
   317  	}
   318  
   319  	sp.expandAddresses(rpt, addrs, flat)
   320  	sp.initSamples(flat, cum)
   321  	return sp
   322  }
   323  
   324  func (sp *sourcePrinter) close() {
   325  	for _, objFile := range sp.objects {
   326  		if objFile != nil {
   327  			objFile.Close()
   328  		}
   329  	}
   330  }
   331  
   332  func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]addrInfo, flat map[uint64]int64) {
   333  	// We found interesting addresses (ones with non-zero samples) above.
   334  	// Get covering address ranges and disassemble the ranges.
   335  	ranges, unprocessed := sp.splitIntoRanges(rpt.prof, addrs, flat)
   336  	sp.handleUnprocessed(addrs, unprocessed)
   337  
   338  	// Trim ranges if there are too many.
   339  	const maxRanges = 25
   340  	sort.Slice(ranges, func(i, j int) bool {
   341  		return ranges[i].score > ranges[j].score
   342  	})
   343  	if len(ranges) > maxRanges {
   344  		ranges = ranges[:maxRanges]
   345  	}
   346  
   347  	for _, r := range ranges {
   348  		objBegin, err := r.obj.ObjAddr(r.begin)
   349  		if err != nil {
   350  			fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range start %x: %v\n", r.begin, err)
   351  			continue
   352  		}
   353  		objEnd, err := r.obj.ObjAddr(r.end)
   354  		if err != nil {
   355  			fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range end %x: %v\n", r.end, err)
   356  			continue
   357  		}
   358  		base := r.begin - objBegin
   359  		insts, err := sp.objectTool.Disasm(r.mapping.File, objBegin, objEnd, rpt.options.IntelSyntax)
   360  		if err != nil {
   361  			// TODO(sanjay): Report that the covered addresses are missing.
   362  			continue
   363  		}
   364  
   365  		var lastFrames []plugin.Frame
   366  		var lastAddr, maxAddr uint64
   367  		for i, inst := range insts {
   368  			addr := inst.Addr + base
   369  
   370  			// Guard against duplicate output from Disasm.
   371  			if addr <= maxAddr {
   372  				continue
   373  			}
   374  			maxAddr = addr
   375  
   376  			length := 1
   377  			if i+1 < len(insts) && insts[i+1].Addr > inst.Addr {
   378  				// Extend to next instruction.
   379  				length = int(insts[i+1].Addr - inst.Addr)
   380  			}
   381  
   382  			// Get inlined-call-stack for address.
   383  			frames, err := r.obj.SourceLine(addr)
   384  			if err != nil {
   385  				// Construct a frame from disassembler output.
   386  				frames = []plugin.Frame{{Func: inst.Function, File: inst.File, Line: inst.Line}}
   387  			}
   388  
   389  			x := instructionInfo{objAddr: inst.Addr, length: length, disasm: inst.Text}
   390  			if len(frames) > 0 {
   391  				// We could consider using the outer-most caller's source
   392  				// location so we give the some hint as to where the
   393  				// inlining happened that led to this instruction. So for
   394  				// example, suppose we have the following (inlined) call
   395  				// chains for this instruction:
   396  				//   F1->G->H
   397  				//   F2->G->H
   398  				// We could tag the instructions from the first call with
   399  				// F1 and instructions from the second call with F2. But
   400  				// that leads to a somewhat confusing display. So for now,
   401  				// we stick with just the inner-most location (i.e., H).
   402  				// In the future we will consider changing the display to
   403  				// make caller info more visible.
   404  				index := 0 // Inner-most frame
   405  				x.file = frames[index].File
   406  				x.line = frames[index].Line
   407  			}
   408  			sp.insts[addr] = x
   409  
   410  			// We sometimes get instructions with a zero reported line number.
   411  			// Make such instructions have the same line info as the preceding
   412  			// instruction, if an earlier instruction is found close enough.
   413  			const neighborhood = 32
   414  			if len(frames) > 0 && frames[0].Line != 0 {
   415  				lastFrames = frames
   416  				lastAddr = addr
   417  			} else if (addr-lastAddr <= neighborhood) && lastFrames != nil {
   418  				frames = lastFrames
   419  			}
   420  
   421  			sp.addStack(addr, frames)
   422  		}
   423  	}
   424  }
   425  
   426  func (sp *sourcePrinter) addStack(addr uint64, frames []plugin.Frame) {
   427  	// See if the stack contains a function we are interested in.
   428  	for i, f := range frames {
   429  		if !sp.interest[f.Func] {
   430  			continue
   431  		}
   432  
   433  		// Record sub-stack under frame's file/line.
   434  		fname := canonicalizeFileName(f.File)
   435  		file := sp.files[fname]
   436  		if file == nil {
   437  			file = &sourceFile{
   438  				fname:    fname,
   439  				lines:    map[int][]sourceInst{},
   440  				funcName: map[int]string{},
   441  			}
   442  			sp.files[fname] = file
   443  		}
   444  		callees := frames[:i]
   445  		stack := make([]callID, 0, len(callees))
   446  		for j := len(callees) - 1; j >= 0; j-- { // Reverse so caller is first
   447  			stack = append(stack, callID{
   448  				file: callees[j].File,
   449  				line: callees[j].Line,
   450  			})
   451  		}
   452  		file.lines[f.Line] = append(file.lines[f.Line], sourceInst{addr, stack})
   453  
   454  		// Remember the first function name encountered per source line
   455  		// and assume that that line belongs to that function.
   456  		if _, ok := file.funcName[f.Line]; !ok {
   457  			file.funcName[f.Line] = f.Func
   458  		}
   459  	}
   460  }
   461  
   462  // synthAsm is the special disassembler value used for instructions without an object file.
   463  const synthAsm = ""
   464  
   465  // handleUnprocessed handles addresses that were skipped by splitIntoRanges because they
   466  // did not belong to a known object file.
   467  func (sp *sourcePrinter) handleUnprocessed(addrs map[uint64]addrInfo, unprocessed []uint64) {
   468  	// makeFrames synthesizes a []plugin.Frame list for the specified address.
   469  	// The result will typically have length 1, but may be longer if address corresponds
   470  	// to inlined calls.
   471  	makeFrames := func(addr uint64) []plugin.Frame {
   472  		loc := addrs[addr].loc
   473  		stack := make([]plugin.Frame, 0, len(loc.Line))
   474  		for _, line := range loc.Line {
   475  			fn := line.Function
   476  			if fn == nil {
   477  				continue
   478  			}
   479  			stack = append(stack, plugin.Frame{
   480  				Func: fn.Name,
   481  				File: fn.Filename,
   482  				Line: int(line.Line),
   483  			})
   484  		}
   485  		return stack
   486  	}
   487  
   488  	for _, addr := range unprocessed {
   489  		frames := makeFrames(addr)
   490  		x := instructionInfo{
   491  			objAddr: addr,
   492  			length:  1,
   493  			disasm:  synthAsm,
   494  		}
   495  		if len(frames) > 0 {
   496  			x.file = frames[0].File
   497  			x.line = frames[0].Line
   498  		}
   499  		sp.insts[addr] = x
   500  
   501  		sp.addStack(addr, frames)
   502  	}
   503  }
   504  
   505  // splitIntoRanges converts the set of addresses we are interested in into a set of address
   506  // ranges to disassemble. It also returns the set of addresses found that did not have an
   507  // associated object file and were therefore not added to an address range.
   508  func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, addrMap map[uint64]addrInfo, flat map[uint64]int64) ([]addressRange, []uint64) {
   509  	// Partition addresses into two sets: ones with a known object file, and ones without.
   510  	var addrs, unprocessed []uint64
   511  	for addr, info := range addrMap {
   512  		if info.obj != nil {
   513  			addrs = append(addrs, addr)
   514  		} else {
   515  			unprocessed = append(unprocessed, addr)
   516  		}
   517  	}
   518  	sort.Slice(addrs, func(i, j int) bool { return addrs[i] < addrs[j] })
   519  
   520  	const expand = 500 // How much to expand range to pick up nearby addresses.
   521  	var result []addressRange
   522  	for i, n := 0, len(addrs); i < n; {
   523  		begin, end := addrs[i], addrs[i]
   524  		sum := flat[begin]
   525  		i++
   526  
   527  		info := addrMap[begin]
   528  		m := info.loc.Mapping
   529  		obj := info.obj // Non-nil because of the partitioning done above.
   530  
   531  		// Find following addresses that are close enough to addrs[i].
   532  		for i < n && addrs[i] <= end+2*expand && addrs[i] < m.Limit {
   533  			// When we expand ranges by "expand" on either side, the ranges
   534  			// for addrs[i] and addrs[i-1] will merge.
   535  			end = addrs[i]
   536  			sum += flat[end]
   537  			i++
   538  		}
   539  		if m.Start-begin >= expand {
   540  			begin -= expand
   541  		} else {
   542  			begin = m.Start
   543  		}
   544  		if m.Limit-end >= expand {
   545  			end += expand
   546  		} else {
   547  			end = m.Limit
   548  		}
   549  
   550  		result = append(result, addressRange{begin, end, obj, m, sum})
   551  	}
   552  	return result, unprocessed
   553  }
   554  
   555  func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) {
   556  	for addr, inst := range sp.insts {
   557  		// Move all samples that were assigned to the middle of an instruction to the
   558  		// beginning of that instruction. This takes care of samples that were recorded
   559  		// against pc+1.
   560  		instEnd := addr + uint64(inst.length)
   561  		for p := addr; p < instEnd; p++ {
   562  			inst.flat += flat[p]
   563  			inst.cum += cum[p]
   564  		}
   565  		sp.insts[addr] = inst
   566  	}
   567  }
   568  
   569  func (sp *sourcePrinter) print(w io.Writer, maxFiles int, rpt *Report) {
   570  	// Finalize per-file counts.
   571  	for _, file := range sp.files {
   572  		seen := map[uint64]bool{}
   573  		for _, line := range file.lines {
   574  			for _, x := range line {
   575  				if seen[x.addr] {
   576  					// Same address can be displayed multiple times in a file
   577  					// (e.g., if we show multiple inlined functions).
   578  					// Avoid double-counting samples in this case.
   579  					continue
   580  				}
   581  				seen[x.addr] = true
   582  				inst := sp.insts[x.addr]
   583  				file.cum += inst.cum
   584  				file.flat += inst.flat
   585  			}
   586  		}
   587  	}
   588  
   589  	// Get sorted list of files to print.
   590  	var files []*sourceFile
   591  	for _, f := range sp.files {
   592  		files = append(files, f)
   593  	}
   594  	order := func(i, j int) bool { return files[i].flat > files[j].flat }
   595  	if maxFiles < 0 {
   596  		// Order by name for compatibility with old code.
   597  		order = func(i, j int) bool { return files[i].fname < files[j].fname }
   598  		maxFiles = len(files)
   599  	}
   600  	sort.Slice(files, order)
   601  	for i, f := range files {
   602  		if i < maxFiles {
   603  			sp.printFile(w, f, rpt)
   604  		}
   605  	}
   606  }
   607  
   608  func (sp *sourcePrinter) printFile(w io.Writer, f *sourceFile, rpt *Report) {
   609  	for _, fn := range sp.functions(f) {
   610  		if fn.cum == 0 {
   611  			continue
   612  		}
   613  		printFunctionHeader(w, fn.name, f.fname, fn.flat, fn.cum, rpt)
   614  		var asm []assemblyInstruction
   615  		for l := fn.begin; l < fn.end; l++ {
   616  			lineContents, ok := sp.reader.line(f.fname, l)
   617  			if !ok {
   618  				if len(f.lines[l]) == 0 {
   619  					// Outside of range of valid lines and nothing to print.
   620  					continue
   621  				}
   622  				if l == 0 {
   623  					// Line number 0 shows up if line number is not known.
   624  					lineContents = "<instructions with unknown line numbers>"
   625  				} else {
   626  					// Past end of file, but have data to print.
   627  					lineContents = "???"
   628  				}
   629  			}
   630  
   631  			// Make list of assembly instructions.
   632  			asm = asm[:0]
   633  			var flatSum, cumSum int64
   634  			var lastAddr uint64
   635  			for _, inst := range f.lines[l] {
   636  				addr := inst.addr
   637  				x := sp.insts[addr]
   638  				flatSum += x.flat
   639  				cumSum += x.cum
   640  				startsBlock := (addr != lastAddr+uint64(sp.insts[lastAddr].length))
   641  				lastAddr = addr
   642  
   643  				// divisors already applied, so leave flatDiv,cumDiv as 0
   644  				asm = append(asm, assemblyInstruction{
   645  					address:     x.objAddr,
   646  					instruction: x.disasm,
   647  					function:    fn.name,
   648  					file:        x.file,
   649  					line:        x.line,
   650  					flat:        x.flat,
   651  					cum:         x.cum,
   652  					startsBlock: startsBlock,
   653  					inlineCalls: inst.stack,
   654  				})
   655  			}
   656  
   657  			printFunctionSourceLine(w, l, flatSum, cumSum, lineContents, asm, sp.reader, rpt)
   658  		}
   659  		printFunctionClosing(w)
   660  	}
   661  }
   662  
   663  // functions splits apart the lines to show in a file into a list of per-function ranges.
   664  func (sp *sourcePrinter) functions(f *sourceFile) []sourceFunction {
   665  	var funcs []sourceFunction
   666  
   667  	// Get interesting lines in sorted order.
   668  	lines := make([]int, 0, len(f.lines))
   669  	for l := range f.lines {
   670  		lines = append(lines, l)
   671  	}
   672  	sort.Ints(lines)
   673  
   674  	// Merge adjacent lines that are in same function and not too far apart.
   675  	const mergeLimit = 20
   676  	for _, l := range lines {
   677  		name := f.funcName[l]
   678  		if pretty, ok := sp.prettyNames[name]; ok {
   679  			// Use demangled name if available.
   680  			name = pretty
   681  		}
   682  
   683  		fn := sourceFunction{name: name, begin: l, end: l + 1}
   684  		for _, x := range f.lines[l] {
   685  			inst := sp.insts[x.addr]
   686  			fn.flat += inst.flat
   687  			fn.cum += inst.cum
   688  		}
   689  
   690  		// See if we should merge into preceding function.
   691  		if len(funcs) > 0 {
   692  			last := funcs[len(funcs)-1]
   693  			if l-last.end < mergeLimit && last.name == name {
   694  				last.end = l + 1
   695  				last.flat += fn.flat
   696  				last.cum += fn.cum
   697  				funcs[len(funcs)-1] = last
   698  				continue
   699  			}
   700  		}
   701  
   702  		// Add new function.
   703  		funcs = append(funcs, fn)
   704  	}
   705  
   706  	// Expand function boundaries to show neighborhood.
   707  	const expand = 5
   708  	for i, f := range funcs {
   709  		if i == 0 {
   710  			// Extend backwards, stopping at line number 1, but do not disturb 0
   711  			// since that is a special line number that can show up when addr2line
   712  			// cannot determine the real line number.
   713  			if f.begin > expand {
   714  				f.begin -= expand
   715  			} else if f.begin > 1 {
   716  				f.begin = 1
   717  			}
   718  		} else {
   719  			// Find gap from predecessor and divide between predecessor and f.
   720  			halfGap := (f.begin - funcs[i-1].end) / 2
   721  			if halfGap > expand {
   722  				halfGap = expand
   723  			}
   724  			funcs[i-1].end += halfGap
   725  			f.begin -= halfGap
   726  		}
   727  		funcs[i] = f
   728  	}
   729  
   730  	// Also extend the ending point of the last function.
   731  	if len(funcs) > 0 {
   732  		funcs[len(funcs)-1].end += expand
   733  	}
   734  
   735  	return funcs
   736  }
   737  
   738  // objectFile return the object for the specified mapping, opening it if necessary.
   739  // It returns nil on error.
   740  func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile {
   741  	if m == nil {
   742  		return nil
   743  	}
   744  	if object, ok := sp.objects[m.File]; ok {
   745  		return object // May be nil if we detected an error earlier.
   746  	}
   747  	object, err := sp.objectTool.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)
   748  	if err != nil {
   749  		object = nil
   750  	}
   751  	sp.objects[m.File] = object // Cache even on error.
   752  	return object
   753  }
   754  
   755  // printHeader prints the page header for a weblist report.
   756  func printHeader(w io.Writer, rpt *Report) {
   757  	fmt.Fprintln(w, `
   758  <!DOCTYPE html>
   759  <html>
   760  <head>
   761  <meta charset="UTF-8">
   762  <title>Pprof listing</title>`)
   763  	fmt.Fprintln(w, weblistPageCSS)
   764  	fmt.Fprintln(w, weblistPageScript)
   765  	fmt.Fprint(w, "</head>\n<body>\n\n")
   766  
   767  	var labels []string
   768  	for _, l := range ProfileLabels(rpt) {
   769  		labels = append(labels, template.HTMLEscapeString(l))
   770  	}
   771  
   772  	fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
   773  		strings.Join(labels, "<br>\n"),
   774  		rpt.formatValue(rpt.total),
   775  	)
   776  }
   777  
   778  // printFunctionHeader prints a function header for a weblist report.
   779  func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
   780  	fmt.Fprintf(w, `<h2>%s</h2><p class="filename">%s</p>
   781  <pre onClick="pprof_toggle_asm(event)">
   782    Total:  %10s %10s (flat, cum) %s
   783  `,
   784  		template.HTMLEscapeString(name), template.HTMLEscapeString(path),
   785  		rpt.formatValue(flatSum), rpt.formatValue(cumSum),
   786  		measurement.Percentage(cumSum, rpt.total))
   787  }
   788  
   789  // printFunctionSourceLine prints a source line and the corresponding assembly.
   790  func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineContents string,
   791  	assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
   792  	if len(assembly) == 0 {
   793  		fmt.Fprintf(w,
   794  			"<span class=line> %6d</span> <span class=nop>  %10s %10s %8s  %s </span>\n",
   795  			lineNo,
   796  			valueOrDot(flat, rpt), valueOrDot(cum, rpt),
   797  			"", template.HTMLEscapeString(lineContents))
   798  		return
   799  	}
   800  
   801  	nestedInfo := false
   802  	cl := "deadsrc"
   803  	for _, an := range assembly {
   804  		if len(an.inlineCalls) > 0 || an.instruction != synthAsm {
   805  			nestedInfo = true
   806  			cl = "livesrc"
   807  		}
   808  	}
   809  
   810  	fmt.Fprintf(w,
   811  		"<span class=line> %6d</span> <span class=%s>  %10s %10s %8s  %s </span>",
   812  		lineNo, cl,
   813  		valueOrDot(flat, rpt), valueOrDot(cum, rpt),
   814  		"", template.HTMLEscapeString(lineContents))
   815  	if nestedInfo {
   816  		srcIndent := indentation(lineContents)
   817  		printNested(w, srcIndent, assembly, reader, rpt)
   818  	}
   819  	fmt.Fprintln(w)
   820  }
   821  
   822  func printNested(w io.Writer, srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
   823  	fmt.Fprint(w, "<span class=asm>")
   824  	var curCalls []callID
   825  	for i, an := range assembly {
   826  		if an.startsBlock && i != 0 {
   827  			// Insert a separator between discontiguous blocks.
   828  			fmt.Fprintf(w, " %8s %28s\n", "", "⋮")
   829  		}
   830  
   831  		var fileline string
   832  		if an.file != "" {
   833  			fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(filepath.Base(an.file)), an.line)
   834  		}
   835  		flat, cum := an.flat, an.cum
   836  
   837  		// Print inlined call context.
   838  		for j, c := range an.inlineCalls {
   839  			if j < len(curCalls) && curCalls[j] == c {
   840  				// Skip if same as previous instruction.
   841  				continue
   842  			}
   843  			curCalls = nil
   844  			fline, ok := reader.line(c.file, c.line)
   845  			if !ok {
   846  				fline = ""
   847  			}
   848  			text := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline)
   849  			fmt.Fprintf(w, " %8s %10s %10s %8s  <span class=inlinesrc>%s</span> <span class=unimportant>%s:%d</span>\n",
   850  				"", "", "", "",
   851  				template.HTMLEscapeString(rightPad(text, 80)),
   852  				template.HTMLEscapeString(filepath.Base(c.file)), c.line)
   853  		}
   854  		curCalls = an.inlineCalls
   855  		if an.instruction == synthAsm {
   856  			continue
   857  		}
   858  		text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
   859  		fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
   860  			"", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address,
   861  			template.HTMLEscapeString(rightPad(text, 80)),
   862  			// fileline should not be escaped since it was formed by appending
   863  			// line number (just digits) to an escaped file name. Escaping here
   864  			// would cause double-escaping of file name.
   865  			fileline)
   866  	}
   867  	fmt.Fprint(w, "</span>")
   868  }
   869  
   870  // printFunctionClosing prints the end of a function in a weblist report.
   871  func printFunctionClosing(w io.Writer) {
   872  	fmt.Fprintln(w, "</pre>")
   873  }
   874  
   875  // printPageClosing prints the end of the page in a weblist report.
   876  func printPageClosing(w io.Writer) {
   877  	fmt.Fprintln(w, weblistPageClosing)
   878  }
   879  
   880  // getSourceFromFile collects the sources of a function from a source
   881  // file and annotates it with the samples in fns. Returns the sources
   882  // as nodes, using the info.name field to hold the source code.
   883  func getSourceFromFile(file string, reader *sourceReader, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
   884  	lineNodes := make(map[int]graph.Nodes)
   885  
   886  	// Collect source coordinates from profile.
   887  	const margin = 5 // Lines before first/after last sample.
   888  	if start == 0 {
   889  		if fns[0].Info.StartLine != 0 {
   890  			start = fns[0].Info.StartLine
   891  		} else {
   892  			start = fns[0].Info.Lineno - margin
   893  		}
   894  	} else {
   895  		start -= margin
   896  	}
   897  	if end == 0 {
   898  		end = fns[0].Info.Lineno
   899  	}
   900  	end += margin
   901  	for _, n := range fns {
   902  		lineno := n.Info.Lineno
   903  		nodeStart := n.Info.StartLine
   904  		if nodeStart == 0 {
   905  			nodeStart = lineno - margin
   906  		}
   907  		nodeEnd := lineno + margin
   908  		if nodeStart < start {
   909  			start = nodeStart
   910  		} else if nodeEnd > end {
   911  			end = nodeEnd
   912  		}
   913  		lineNodes[lineno] = append(lineNodes[lineno], n)
   914  	}
   915  	if start < 1 {
   916  		start = 1
   917  	}
   918  
   919  	var src graph.Nodes
   920  	for lineno := start; lineno <= end; lineno++ {
   921  		line, ok := reader.line(file, lineno)
   922  		if !ok {
   923  			break
   924  		}
   925  		flat, cum := lineNodes[lineno].Sum()
   926  		src = append(src, &graph.Node{
   927  			Info: graph.NodeInfo{
   928  				Name:   strings.TrimRight(line, "\n"),
   929  				Lineno: lineno,
   930  			},
   931  			Flat: flat,
   932  			Cum:  cum,
   933  		})
   934  	}
   935  	if err := reader.fileError(file); err != nil {
   936  		return nil, file, err
   937  	}
   938  	return src, file, nil
   939  }
   940  
   941  // sourceReader provides access to source code with caching of file contents.
   942  type sourceReader struct {
   943  	// searchPath is a filepath.ListSeparator-separated list of directories where
   944  	// source files should be searched.
   945  	searchPath string
   946  
   947  	// trimPath is a filepath.ListSeparator-separated list of paths to trim.
   948  	trimPath string
   949  
   950  	// files maps from path name to a list of lines.
   951  	// files[*][0] is unused since line numbering starts at 1.
   952  	files map[string][]string
   953  
   954  	// errors collects errors encountered per file. These errors are
   955  	// consulted before returning out of these module.
   956  	errors map[string]error
   957  }
   958  
   959  func newSourceReader(searchPath, trimPath string) *sourceReader {
   960  	return &sourceReader{
   961  		searchPath,
   962  		trimPath,
   963  		make(map[string][]string),
   964  		make(map[string]error),
   965  	}
   966  }
   967  
   968  func (reader *sourceReader) fileError(path string) error {
   969  	return reader.errors[path]
   970  }
   971  
   972  // line returns the line numbered "lineno" in path, or _,false if lineno is out of range.
   973  func (reader *sourceReader) line(path string, lineno int) (string, bool) {
   974  	lines, ok := reader.files[path]
   975  	if !ok {
   976  		// Read and cache file contents.
   977  		lines = []string{""} // Skip 0th line
   978  		f, err := openSourceFile(path, reader.searchPath, reader.trimPath)
   979  		if err != nil {
   980  			reader.errors[path] = err
   981  		} else {
   982  			s := bufio.NewScanner(f)
   983  			for s.Scan() {
   984  				lines = append(lines, s.Text())
   985  			}
   986  			f.Close()
   987  			if s.Err() != nil {
   988  				reader.errors[path] = err
   989  			}
   990  		}
   991  		reader.files[path] = lines
   992  	}
   993  	if lineno <= 0 || lineno >= len(lines) {
   994  		return "", false
   995  	}
   996  	return lines[lineno], true
   997  }
   998  
   999  // openSourceFile opens a source file from a name encoded in a profile. File
  1000  // names in a profile after can be relative paths, so search them in each of
  1001  // the paths in searchPath and their parents. In case the profile contains
  1002  // absolute paths, additional paths may be configured to trim from the source
  1003  // paths in the profile. This effectively turns the path into a relative path
  1004  // searching it using searchPath as usual).
  1005  func openSourceFile(path, searchPath, trim string) (*os.File, error) {
  1006  	path = trimPath(path, trim, searchPath)
  1007  	// If file is still absolute, require file to exist.
  1008  	if filepath.IsAbs(path) {
  1009  		f, err := os.Open(path)
  1010  		return f, err
  1011  	}
  1012  	// Scan each component of the path.
  1013  	for _, dir := range filepath.SplitList(searchPath) {
  1014  		// Search up for every parent of each possible path.
  1015  		for {
  1016  			filename := filepath.Join(dir, path)
  1017  			if f, err := os.Open(filename); err == nil {
  1018  				return f, nil
  1019  			}
  1020  			parent := filepath.Dir(dir)
  1021  			if parent == dir {
  1022  				break
  1023  			}
  1024  			dir = parent
  1025  		}
  1026  	}
  1027  
  1028  	return nil, fmt.Errorf("could not find file %s on path %s", path, searchPath)
  1029  }
  1030  
  1031  // trimPath cleans up a path by removing prefixes that are commonly
  1032  // found on profiles plus configured prefixes.
  1033  // TODO(aalexand): Consider optimizing out the redundant work done in this
  1034  // function if it proves to matter.
  1035  func trimPath(path, trimPath, searchPath string) string {
  1036  	// Keep path variable intact as it's used below to form the return value.
  1037  	sPath, searchPath := filepath.ToSlash(path), filepath.ToSlash(searchPath)
  1038  	if trimPath == "" {
  1039  		// If the trim path is not configured, try to guess it heuristically:
  1040  		// search for basename of each search path in the original path and, if
  1041  		// found, strip everything up to and including the basename. So, for
  1042  		// example, given original path "/some/remote/path/my-project/foo/bar.c"
  1043  		// and search path "/my/local/path/my-project" the heuristic will return
  1044  		// "/my/local/path/my-project/foo/bar.c".
  1045  		for _, dir := range filepath.SplitList(searchPath) {
  1046  			want := "/" + filepath.Base(dir) + "/"
  1047  			if found := strings.Index(sPath, want); found != -1 {
  1048  				return path[found+len(want):]
  1049  			}
  1050  		}
  1051  	}
  1052  	// Trim configured trim prefixes.
  1053  	trimPaths := append(filepath.SplitList(filepath.ToSlash(trimPath)), "/proc/self/cwd/./", "/proc/self/cwd/")
  1054  	for _, trimPath := range trimPaths {
  1055  		if !strings.HasSuffix(trimPath, "/") {
  1056  			trimPath += "/"
  1057  		}
  1058  		if strings.HasPrefix(sPath, trimPath) {
  1059  			return path[len(trimPath):]
  1060  		}
  1061  	}
  1062  	return path
  1063  }
  1064  
  1065  func indentation(line string) int {
  1066  	column := 0
  1067  	for _, c := range line {
  1068  		if c == ' ' {
  1069  			column++
  1070  		} else if c == '\t' {
  1071  			column++
  1072  			for column%8 != 0 {
  1073  				column++
  1074  			}
  1075  		} else {
  1076  			break
  1077  		}
  1078  	}
  1079  	return column
  1080  }
  1081  
  1082  // rightPad pads the input with spaces on the right-hand-side to make it have
  1083  // at least width n. It treats tabs as enough spaces that lead to the next
  1084  // 8-aligned tab-stop.
  1085  func rightPad(s string, n int) string {
  1086  	var str strings.Builder
  1087  
  1088  	// Convert tabs to spaces as we go so padding works regardless of what prefix
  1089  	// is placed before the result.
  1090  	column := 0
  1091  	for _, c := range s {
  1092  		column++
  1093  		if c == '\t' {
  1094  			str.WriteRune(' ')
  1095  			for column%8 != 0 {
  1096  				column++
  1097  				str.WriteRune(' ')
  1098  			}
  1099  		} else {
  1100  			str.WriteRune(c)
  1101  		}
  1102  	}
  1103  	for column < n {
  1104  		column++
  1105  		str.WriteRune(' ')
  1106  	}
  1107  	return str.String()
  1108  }
  1109  
  1110  func canonicalizeFileName(fname string) string {
  1111  	fname = strings.TrimPrefix(fname, "/proc/self/cwd/")
  1112  	fname = strings.TrimPrefix(fname, "./")
  1113  	return filepath.Clean(fname)
  1114  }
  1115  

View as plain text