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

View as plain text