...

Source file src/cmd/vendor/github.com/google/pprof/internal/report/report.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 summarizes a performance profile into a
    16	// human-readable report.
    17	package report
    18	
    19	import (
    20		"fmt"
    21		"io"
    22		"path/filepath"
    23		"regexp"
    24		"sort"
    25		"strconv"
    26		"strings"
    27		"text/tabwriter"
    28		"time"
    29	
    30		"github.com/google/pprof/internal/graph"
    31		"github.com/google/pprof/internal/measurement"
    32		"github.com/google/pprof/internal/plugin"
    33		"github.com/google/pprof/profile"
    34	)
    35	
    36	// Output formats.
    37	const (
    38		Callgrind = iota
    39		Comments
    40		Dis
    41		Dot
    42		List
    43		Proto
    44		Raw
    45		Tags
    46		Text
    47		TopProto
    48		Traces
    49		Tree
    50		WebList
    51	)
    52	
    53	// Options are the formatting and filtering options used to generate a
    54	// profile.
    55	type Options struct {
    56		OutputFormat int
    57	
    58		CumSort       bool
    59		CallTree      bool
    60		DropNegative  bool
    61		CompactLabels bool
    62		Ratio         float64
    63		Title         string
    64		ProfileLabels []string
    65		ActiveFilters []string
    66		NumLabelUnits map[string]string
    67	
    68		NodeCount    int
    69		NodeFraction float64
    70		EdgeFraction float64
    71	
    72		SampleValue       func(s []int64) int64
    73		SampleMeanDivisor func(s []int64) int64
    74		SampleType        string
    75		SampleUnit        string // Unit for the sample data from the profile.
    76	
    77		OutputUnit string // Units for data formatting in report.
    78	
    79		Symbol     *regexp.Regexp // Symbols to include on disassembly report.
    80		SourcePath string         // Search path for source files.
    81		TrimPath   string         // Paths to trim from source file paths.
    82	}
    83	
    84	// Generate generates a report as directed by the Report.
    85	func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
    86		o := rpt.options
    87	
    88		switch o.OutputFormat {
    89		case Comments:
    90			return printComments(w, rpt)
    91		case Dot:
    92			return printDOT(w, rpt)
    93		case Tree:
    94			return printTree(w, rpt)
    95		case Text:
    96			return printText(w, rpt)
    97		case Traces:
    98			return printTraces(w, rpt)
    99		case Raw:
   100			fmt.Fprint(w, rpt.prof.String())
   101			return nil
   102		case Tags:
   103			return printTags(w, rpt)
   104		case Proto:
   105			return rpt.prof.Write(w)
   106		case TopProto:
   107			return printTopProto(w, rpt)
   108		case Dis:
   109			return printAssembly(w, rpt, obj)
   110		case List:
   111			return printSource(w, rpt)
   112		case WebList:
   113			return printWebSource(w, rpt, obj)
   114		case Callgrind:
   115			return printCallgrind(w, rpt)
   116		}
   117		return fmt.Errorf("unexpected output format")
   118	}
   119	
   120	// newTrimmedGraph creates a graph for this report, trimmed according
   121	// to the report options.
   122	func (rpt *Report) newTrimmedGraph() (g *graph.Graph, origCount, droppedNodes, droppedEdges int) {
   123		o := rpt.options
   124	
   125		// Build a graph and refine it. On each refinement step we must rebuild the graph from the samples,
   126		// as the graph itself doesn't contain enough information to preserve full precision.
   127		visualMode := o.OutputFormat == Dot
   128		cumSort := o.CumSort
   129	
   130		// The call_tree option is only honored when generating visual representations of the callgraph.
   131		callTree := o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind)
   132	
   133		// First step: Build complete graph to identify low frequency nodes, based on their cum weight.
   134		g = rpt.newGraph(nil)
   135		totalValue, _ := g.Nodes.Sum()
   136		nodeCutoff := abs64(int64(float64(totalValue) * o.NodeFraction))
   137		edgeCutoff := abs64(int64(float64(totalValue) * o.EdgeFraction))
   138	
   139		// Filter out nodes with cum value below nodeCutoff.
   140		if nodeCutoff > 0 {
   141			if callTree {
   142				if nodesKept := g.DiscardLowFrequencyNodePtrs(nodeCutoff); len(g.Nodes) != len(nodesKept) {
   143					droppedNodes = len(g.Nodes) - len(nodesKept)
   144					g.TrimTree(nodesKept)
   145				}
   146			} else {
   147				if nodesKept := g.DiscardLowFrequencyNodes(nodeCutoff); len(g.Nodes) != len(nodesKept) {
   148					droppedNodes = len(g.Nodes) - len(nodesKept)
   149					g = rpt.newGraph(nodesKept)
   150				}
   151			}
   152		}
   153		origCount = len(g.Nodes)
   154	
   155		// Second step: Limit the total number of nodes. Apply specialized heuristics to improve
   156		// visualization when generating dot output.
   157		g.SortNodes(cumSort, visualMode)
   158		if nodeCount := o.NodeCount; nodeCount > 0 {
   159			// Remove low frequency tags and edges as they affect selection.
   160			g.TrimLowFrequencyTags(nodeCutoff)
   161			g.TrimLowFrequencyEdges(edgeCutoff)
   162			if callTree {
   163				if nodesKept := g.SelectTopNodePtrs(nodeCount, visualMode); len(g.Nodes) != len(nodesKept) {
   164					g.TrimTree(nodesKept)
   165					g.SortNodes(cumSort, visualMode)
   166				}
   167			} else {
   168				if nodesKept := g.SelectTopNodes(nodeCount, visualMode); len(g.Nodes) != len(nodesKept) {
   169					g = rpt.newGraph(nodesKept)
   170					g.SortNodes(cumSort, visualMode)
   171				}
   172			}
   173		}
   174	
   175		// Final step: Filter out low frequency tags and edges, and remove redundant edges that clutter
   176		// the graph.
   177		g.TrimLowFrequencyTags(nodeCutoff)
   178		droppedEdges = g.TrimLowFrequencyEdges(edgeCutoff)
   179		if visualMode {
   180			g.RemoveRedundantEdges()
   181		}
   182		return
   183	}
   184	
   185	func (rpt *Report) selectOutputUnit(g *graph.Graph) {
   186		o := rpt.options
   187	
   188		// Select best unit for profile output.
   189		// Find the appropriate units for the smallest non-zero sample
   190		if o.OutputUnit != "minimum" || len(g.Nodes) == 0 {
   191			return
   192		}
   193		var minValue int64
   194	
   195		for _, n := range g.Nodes {
   196			nodeMin := abs64(n.FlatValue())
   197			if nodeMin == 0 {
   198				nodeMin = abs64(n.CumValue())
   199			}
   200			if nodeMin > 0 && (minValue == 0 || nodeMin < minValue) {
   201				minValue = nodeMin
   202			}
   203		}
   204		maxValue := rpt.total
   205		if minValue == 0 {
   206			minValue = maxValue
   207		}
   208	
   209		if r := o.Ratio; r > 0 && r != 1 {
   210			minValue = int64(float64(minValue) * r)
   211			maxValue = int64(float64(maxValue) * r)
   212		}
   213	
   214		_, minUnit := measurement.Scale(minValue, o.SampleUnit, "minimum")
   215		_, maxUnit := measurement.Scale(maxValue, o.SampleUnit, "minimum")
   216	
   217		unit := minUnit
   218		if minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind {
   219			// Minimum and maximum values have different units. Scale
   220			// minimum by 100 to use larger units, allowing minimum value to
   221			// be scaled down to 0.01, except for callgrind reports since
   222			// they can only represent integer values.
   223			_, unit = measurement.Scale(100*minValue, o.SampleUnit, "minimum")
   224		}
   225	
   226		if unit != "" {
   227			o.OutputUnit = unit
   228		} else {
   229			o.OutputUnit = o.SampleUnit
   230		}
   231	}
   232	
   233	// newGraph creates a new graph for this report. If nodes is non-nil,
   234	// only nodes whose info matches are included. Otherwise, all nodes
   235	// are included, without trimming.
   236	func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph {
   237		o := rpt.options
   238	
   239		// Clean up file paths using heuristics.
   240		prof := rpt.prof
   241		for _, f := range prof.Function {
   242			f.Filename = trimPath(f.Filename, o.TrimPath, o.SourcePath)
   243		}
   244		// Removes all numeric tags except for the bytes tag prior
   245		// to making graph.
   246		// TODO: modify to select first numeric tag if no bytes tag
   247		for _, s := range prof.Sample {
   248			numLabels := make(map[string][]int64, len(s.NumLabel))
   249			numUnits := make(map[string][]string, len(s.NumLabel))
   250			for k, vs := range s.NumLabel {
   251				if k == "bytes" {
   252					unit := o.NumLabelUnits[k]
   253					numValues := make([]int64, len(vs))
   254					numUnit := make([]string, len(vs))
   255					for i, v := range vs {
   256						numValues[i] = v
   257						numUnit[i] = unit
   258					}
   259					numLabels[k] = append(numLabels[k], numValues...)
   260					numUnits[k] = append(numUnits[k], numUnit...)
   261				}
   262			}
   263			s.NumLabel = numLabels
   264			s.NumUnit = numUnits
   265		}
   266	
   267		// Remove label marking samples from the base profiles, so it does not appear
   268		// as a nodelet in the graph view.
   269		prof.RemoveLabel("pprof::base")
   270	
   271		formatTag := func(v int64, key string) string {
   272			return measurement.ScaledLabel(v, key, o.OutputUnit)
   273		}
   274	
   275		gopt := &graph.Options{
   276			SampleValue:       o.SampleValue,
   277			SampleMeanDivisor: o.SampleMeanDivisor,
   278			FormatTag:         formatTag,
   279			CallTree:          o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind),
   280			DropNegative:      o.DropNegative,
   281			KeptNodes:         nodes,
   282		}
   283	
   284		// Only keep binary names for disassembly-based reports, otherwise
   285		// remove it to allow merging of functions across binaries.
   286		switch o.OutputFormat {
   287		case Raw, List, WebList, Dis, Callgrind:
   288			gopt.ObjNames = true
   289		}
   290	
   291		return graph.New(rpt.prof, gopt)
   292	}
   293	
   294	func printTopProto(w io.Writer, rpt *Report) error {
   295		p := rpt.prof
   296		o := rpt.options
   297		g, _, _, _ := rpt.newTrimmedGraph()
   298		rpt.selectOutputUnit(g)
   299	
   300		out := profile.Profile{
   301			SampleType: []*profile.ValueType{
   302				{Type: "cum", Unit: o.OutputUnit},
   303				{Type: "flat", Unit: o.OutputUnit},
   304			},
   305			TimeNanos:     p.TimeNanos,
   306			DurationNanos: p.DurationNanos,
   307			PeriodType:    p.PeriodType,
   308			Period:        p.Period,
   309		}
   310		functionMap := make(functionMap)
   311		for i, n := range g.Nodes {
   312			f, added := functionMap.findOrAdd(n.Info)
   313			if added {
   314				out.Function = append(out.Function, f)
   315			}
   316			flat, cum := n.FlatValue(), n.CumValue()
   317			l := &profile.Location{
   318				ID:      uint64(i + 1),
   319				Address: n.Info.Address,
   320				Line: []profile.Line{
   321					{
   322						Line:     int64(n.Info.Lineno),
   323						Function: f,
   324					},
   325				},
   326			}
   327	
   328			fv, _ := measurement.Scale(flat, o.SampleUnit, o.OutputUnit)
   329			cv, _ := measurement.Scale(cum, o.SampleUnit, o.OutputUnit)
   330			s := &profile.Sample{
   331				Location: []*profile.Location{l},
   332				Value:    []int64{int64(cv), int64(fv)},
   333			}
   334			out.Location = append(out.Location, l)
   335			out.Sample = append(out.Sample, s)
   336		}
   337	
   338		return out.Write(w)
   339	}
   340	
   341	type functionMap map[string]*profile.Function
   342	
   343	// findOrAdd takes a node representing a function, adds the function
   344	// represented by the node to the map if the function is not already present,
   345	// and returns the function the node represents. This also returns a boolean,
   346	// which is true if the function was added and false otherwise.
   347	func (fm functionMap) findOrAdd(ni graph.NodeInfo) (*profile.Function, bool) {
   348		fName := fmt.Sprintf("%q%q%q%d", ni.Name, ni.OrigName, ni.File, ni.StartLine)
   349	
   350		if f := fm[fName]; f != nil {
   351			return f, false
   352		}
   353	
   354		f := &profile.Function{
   355			ID:         uint64(len(fm) + 1),
   356			Name:       ni.Name,
   357			SystemName: ni.OrigName,
   358			Filename:   ni.File,
   359			StartLine:  int64(ni.StartLine),
   360		}
   361		fm[fName] = f
   362		return f, true
   363	}
   364	
   365	// printAssembly prints an annotated assembly listing.
   366	func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
   367		return PrintAssembly(w, rpt, obj, -1)
   368	}
   369	
   370	// PrintAssembly prints annotated disassembly of rpt to w.
   371	func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) error {
   372		o := rpt.options
   373		prof := rpt.prof
   374	
   375		g := rpt.newGraph(nil)
   376	
   377		// If the regexp source can be parsed as an address, also match
   378		// functions that land on that address.
   379		var address *uint64
   380		if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
   381			address = &hex
   382		}
   383	
   384		fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total))
   385		symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)
   386		symNodes := nodesPerSymbol(g.Nodes, symbols)
   387	
   388		// Sort for printing.
   389		var syms []*objSymbol
   390		for s := range symNodes {
   391			syms = append(syms, s)
   392		}
   393		byName := func(a, b *objSymbol) bool {
   394			if na, nb := a.sym.Name[0], b.sym.Name[0]; na != nb {
   395				return na < nb
   396			}
   397			return a.sym.Start < b.sym.Start
   398		}
   399		if maxFuncs < 0 {
   400			sort.Sort(orderSyms{syms, byName})
   401		} else {
   402			byFlatSum := func(a, b *objSymbol) bool {
   403				suma, _ := symNodes[a].Sum()
   404				sumb, _ := symNodes[b].Sum()
   405				if suma != sumb {
   406					return suma > sumb
   407				}
   408				return byName(a, b)
   409			}
   410			sort.Sort(orderSyms{syms, byFlatSum})
   411			if len(syms) > maxFuncs {
   412				syms = syms[:maxFuncs]
   413			}
   414		}
   415	
   416		// Correlate the symbols from the binary with the profile samples.
   417		for _, s := range syms {
   418			sns := symNodes[s]
   419	
   420			// Gather samples for this symbol.
   421			flatSum, cumSum := sns.Sum()
   422	
   423			// Get the function assembly.
   424			insts, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End)
   425			if err != nil {
   426				return err
   427			}
   428	
   429			ns := annotateAssembly(insts, sns, s.base)
   430	
   431			fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
   432			for _, name := range s.sym.Name[1:] {
   433				fmt.Fprintf(w, "    AKA ======================== %s\n", name)
   434			}
   435			fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
   436				rpt.formatValue(flatSum), rpt.formatValue(cumSum),
   437				measurement.Percentage(cumSum, rpt.total))
   438	
   439			function, file, line := "", "", 0
   440			for _, n := range ns {
   441				locStr := ""
   442				// Skip loc information if it hasn't changed from previous instruction.
   443				if n.function != function || n.file != file || n.line != line {
   444					function, file, line = n.function, n.file, n.line
   445					if n.function != "" {
   446						locStr = n.function + " "
   447					}
   448					if n.file != "" {
   449						locStr += n.file
   450						if n.line != 0 {
   451							locStr += fmt.Sprintf(":%d", n.line)
   452						}
   453					}
   454				}
   455				switch {
   456				case locStr == "":
   457					// No location info, just print the instruction.
   458					fmt.Fprintf(w, "%10s %10s %10x: %s\n",
   459						valueOrDot(n.flatValue(), rpt),
   460						valueOrDot(n.cumValue(), rpt),
   461						n.address, n.instruction,
   462					)
   463				case len(n.instruction) < 40:
   464					// Short instruction, print loc on the same line.
   465					fmt.Fprintf(w, "%10s %10s %10x: %-40s;%s\n",
   466						valueOrDot(n.flatValue(), rpt),
   467						valueOrDot(n.cumValue(), rpt),
   468						n.address, n.instruction,
   469						locStr,
   470					)
   471				default:
   472					// Long instruction, print loc on a separate line.
   473					fmt.Fprintf(w, "%74s;%s\n", "", locStr)
   474					fmt.Fprintf(w, "%10s %10s %10x: %s\n",
   475						valueOrDot(n.flatValue(), rpt),
   476						valueOrDot(n.cumValue(), rpt),
   477						n.address, n.instruction,
   478					)
   479				}
   480			}
   481		}
   482		return nil
   483	}
   484	
   485	// symbolsFromBinaries examines the binaries listed on the profile
   486	// that have associated samples, and identifies symbols matching rx.
   487	func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {
   488		hasSamples := make(map[string]bool)
   489		// Only examine mappings that have samples that match the
   490		// regexp. This is an optimization to speed up pprof.
   491		for _, n := range g.Nodes {
   492			if name := n.Info.PrintableName(); rx.MatchString(name) && n.Info.Objfile != "" {
   493				hasSamples[n.Info.Objfile] = true
   494			}
   495		}
   496	
   497		// Walk all mappings looking for matching functions with samples.
   498		var objSyms []*objSymbol
   499		for _, m := range prof.Mapping {
   500			if !hasSamples[m.File] {
   501				if address == nil || !(m.Start <= *address && *address <= m.Limit) {
   502					continue
   503				}
   504			}
   505	
   506			f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
   507			if err != nil {
   508				fmt.Printf("%v\n", err)
   509				continue
   510			}
   511	
   512			// Find symbols in this binary matching the user regexp.
   513			var addr uint64
   514			if address != nil {
   515				addr = *address
   516			}
   517			msyms, err := f.Symbols(rx, addr)
   518			base := f.Base()
   519			f.Close()
   520			if err != nil {
   521				continue
   522			}
   523			for _, ms := range msyms {
   524				objSyms = append(objSyms,
   525					&objSymbol{
   526						sym:  ms,
   527						base: base,
   528						file: f,
   529					},
   530				)
   531			}
   532		}
   533	
   534		return objSyms
   535	}
   536	
   537	// objSym represents a symbol identified from a binary. It includes
   538	// the SymbolInfo from the disasm package and the base that must be
   539	// added to correspond to sample addresses
   540	type objSymbol struct {
   541		sym  *plugin.Sym
   542		base uint64
   543		file plugin.ObjFile
   544	}
   545	
   546	// orderSyms is a wrapper type to sort []*objSymbol by a supplied comparator.
   547	type orderSyms struct {
   548		v    []*objSymbol
   549		less func(a, b *objSymbol) bool
   550	}
   551	
   552	func (o orderSyms) Len() int           { return len(o.v) }
   553	func (o orderSyms) Less(i, j int) bool { return o.less(o.v[i], o.v[j]) }
   554	func (o orderSyms) Swap(i, j int)      { o.v[i], o.v[j] = o.v[j], o.v[i] }
   555	
   556	// nodesPerSymbol classifies nodes into a group of symbols.
   557	func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {
   558		symNodes := make(map[*objSymbol]graph.Nodes)
   559		for _, s := range symbols {
   560			// Gather samples for this symbol.
   561			for _, n := range ns {
   562				address := n.Info.Address - s.base
   563				if address >= s.sym.Start && address < s.sym.End {
   564					symNodes[s] = append(symNodes[s], n)
   565				}
   566			}
   567		}
   568		return symNodes
   569	}
   570	
   571	type assemblyInstruction struct {
   572		address         uint64
   573		instruction     string
   574		function        string
   575		file            string
   576		line            int
   577		flat, cum       int64
   578		flatDiv, cumDiv int64
   579		startsBlock     bool
   580		inlineCalls     []callID
   581	}
   582	
   583	type callID struct {
   584		file string
   585		line int
   586	}
   587	
   588	func (a *assemblyInstruction) flatValue() int64 {
   589		if a.flatDiv != 0 {
   590			return a.flat / a.flatDiv
   591		}
   592		return a.flat
   593	}
   594	
   595	func (a *assemblyInstruction) cumValue() int64 {
   596		if a.cumDiv != 0 {
   597			return a.cum / a.cumDiv
   598		}
   599		return a.cum
   600	}
   601	
   602	// annotateAssembly annotates a set of assembly instructions with a
   603	// set of samples. It returns a set of nodes to display. base is an
   604	// offset to adjust the sample addresses.
   605	func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, base uint64) []assemblyInstruction {
   606		// Add end marker to simplify printing loop.
   607		insts = append(insts, plugin.Inst{
   608			Addr: ^uint64(0),
   609		})
   610	
   611		// Ensure samples are sorted by address.
   612		samples.Sort(graph.AddressOrder)
   613	
   614		s := 0
   615		asm := make([]assemblyInstruction, 0, len(insts))
   616		for ix, in := range insts[:len(insts)-1] {
   617			n := assemblyInstruction{
   618				address:     in.Addr,
   619				instruction: in.Text,
   620				function:    in.Function,
   621				line:        in.Line,
   622			}
   623			if in.File != "" {
   624				n.file = filepath.Base(in.File)
   625			}
   626	
   627			// Sum all the samples until the next instruction (to account
   628			// for samples attributed to the middle of an instruction).
   629			for next := insts[ix+1].Addr; s < len(samples) && samples[s].Info.Address-base < next; s++ {
   630				sample := samples[s]
   631				n.flatDiv += sample.FlatDiv
   632				n.flat += sample.Flat
   633				n.cumDiv += sample.CumDiv
   634				n.cum += sample.Cum
   635				if f := sample.Info.File; f != "" && n.file == "" {
   636					n.file = filepath.Base(f)
   637				}
   638				if ln := sample.Info.Lineno; ln != 0 && n.line == 0 {
   639					n.line = ln
   640				}
   641				if f := sample.Info.Name; f != "" && n.function == "" {
   642					n.function = f
   643				}
   644			}
   645			asm = append(asm, n)
   646		}
   647	
   648		return asm
   649	}
   650	
   651	// valueOrDot formats a value according to a report, intercepting zero
   652	// values.
   653	func valueOrDot(value int64, rpt *Report) string {
   654		if value == 0 {
   655			return "."
   656		}
   657		return rpt.formatValue(value)
   658	}
   659	
   660	// printTags collects all tags referenced in the profile and prints
   661	// them in a sorted table.
   662	func printTags(w io.Writer, rpt *Report) error {
   663		p := rpt.prof
   664	
   665		o := rpt.options
   666		formatTag := func(v int64, key string) string {
   667			return measurement.ScaledLabel(v, key, o.OutputUnit)
   668		}
   669	
   670		// Hashtable to keep accumulate tags as key,value,count.
   671		tagMap := make(map[string]map[string]int64)
   672		for _, s := range p.Sample {
   673			for key, vals := range s.Label {
   674				for _, val := range vals {
   675					valueMap, ok := tagMap[key]
   676					if !ok {
   677						valueMap = make(map[string]int64)
   678						tagMap[key] = valueMap
   679					}
   680					valueMap[val] += o.SampleValue(s.Value)
   681				}
   682			}
   683			for key, vals := range s.NumLabel {
   684				unit := o.NumLabelUnits[key]
   685				for _, nval := range vals {
   686					val := formatTag(nval, unit)
   687					valueMap, ok := tagMap[key]
   688					if !ok {
   689						valueMap = make(map[string]int64)
   690						tagMap[key] = valueMap
   691					}
   692					valueMap[val] += o.SampleValue(s.Value)
   693				}
   694			}
   695		}
   696	
   697		tagKeys := make([]*graph.Tag, 0, len(tagMap))
   698		for key := range tagMap {
   699			tagKeys = append(tagKeys, &graph.Tag{Name: key})
   700		}
   701		tabw := tabwriter.NewWriter(w, 0, 0, 1, ' ', tabwriter.AlignRight)
   702		for _, tagKey := range graph.SortTags(tagKeys, true) {
   703			var total int64
   704			key := tagKey.Name
   705			tags := make([]*graph.Tag, 0, len(tagMap[key]))
   706			for t, c := range tagMap[key] {
   707				total += c
   708				tags = append(tags, &graph.Tag{Name: t, Flat: c})
   709			}
   710	
   711			f, u := measurement.Scale(total, o.SampleUnit, o.OutputUnit)
   712			fmt.Fprintf(tabw, "%s:\t Total %.1f%s\n", key, f, u)
   713			for _, t := range graph.SortTags(tags, true) {
   714				f, u := measurement.Scale(t.FlatValue(), o.SampleUnit, o.OutputUnit)
   715				if total > 0 {
   716					fmt.Fprintf(tabw, " \t%.1f%s (%s):\t %s\n", f, u, measurement.Percentage(t.FlatValue(), total), t.Name)
   717				} else {
   718					fmt.Fprintf(tabw, " \t%.1f%s:\t %s\n", f, u, t.Name)
   719				}
   720			}
   721			fmt.Fprintln(tabw)
   722		}
   723		return tabw.Flush()
   724	}
   725	
   726	// printComments prints all freeform comments in the profile.
   727	func printComments(w io.Writer, rpt *Report) error {
   728		p := rpt.prof
   729	
   730		for _, c := range p.Comments {
   731			fmt.Fprintln(w, c)
   732		}
   733		return nil
   734	}
   735	
   736	// TextItem holds a single text report entry.
   737	type TextItem struct {
   738		Name                  string
   739		InlineLabel           string // Not empty if inlined
   740		Flat, Cum             int64  // Raw values
   741		FlatFormat, CumFormat string // Formatted values
   742	}
   743	
   744	// TextItems returns a list of text items from the report and a list
   745	// of labels that describe the report.
   746	func TextItems(rpt *Report) ([]TextItem, []string) {
   747		g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
   748		rpt.selectOutputUnit(g)
   749		labels := reportLabels(rpt, g, origCount, droppedNodes, 0, false)
   750	
   751		var items []TextItem
   752		var flatSum int64
   753		for _, n := range g.Nodes {
   754			name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
   755	
   756			var inline, noinline bool
   757			for _, e := range n.In {
   758				if e.Inline {
   759					inline = true
   760				} else {
   761					noinline = true
   762				}
   763			}
   764	
   765			var inl string
   766			if inline {
   767				if noinline {
   768					inl = "(partial-inline)"
   769				} else {
   770					inl = "(inline)"
   771				}
   772			}
   773	
   774			flatSum += flat
   775			items = append(items, TextItem{
   776				Name:        name,
   777				InlineLabel: inl,
   778				Flat:        flat,
   779				Cum:         cum,
   780				FlatFormat:  rpt.formatValue(flat),
   781				CumFormat:   rpt.formatValue(cum),
   782			})
   783		}
   784		return items, labels
   785	}
   786	
   787	// printText prints a flat text report for a profile.
   788	func printText(w io.Writer, rpt *Report) error {
   789		items, labels := TextItems(rpt)
   790		fmt.Fprintln(w, strings.Join(labels, "\n"))
   791		fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n",
   792			"flat", "flat", "sum", "cum", "cum")
   793		var flatSum int64
   794		for _, item := range items {
   795			inl := item.InlineLabel
   796			if inl != "" {
   797				inl = " " + inl
   798			}
   799			flatSum += item.Flat
   800			fmt.Fprintf(w, "%10s %s %s %10s %s  %s%s\n",
   801				item.FlatFormat, measurement.Percentage(item.Flat, rpt.total),
   802				measurement.Percentage(flatSum, rpt.total),
   803				item.CumFormat, measurement.Percentage(item.Cum, rpt.total),
   804				item.Name, inl)
   805		}
   806		return nil
   807	}
   808	
   809	// printTraces prints all traces from a profile.
   810	func printTraces(w io.Writer, rpt *Report) error {
   811		fmt.Fprintln(w, strings.Join(ProfileLabels(rpt), "\n"))
   812	
   813		prof := rpt.prof
   814		o := rpt.options
   815	
   816		const separator = "-----------+-------------------------------------------------------"
   817	
   818		_, locations := graph.CreateNodes(prof, &graph.Options{})
   819		for _, sample := range prof.Sample {
   820			var stack graph.Nodes
   821			for _, loc := range sample.Location {
   822				id := loc.ID
   823				stack = append(stack, locations[id]...)
   824			}
   825	
   826			if len(stack) == 0 {
   827				continue
   828			}
   829	
   830			fmt.Fprintln(w, separator)
   831			// Print any text labels for the sample.
   832			var labels []string
   833			for s, vs := range sample.Label {
   834				labels = append(labels, fmt.Sprintf("%10s:  %s\n", s, strings.Join(vs, " ")))
   835			}
   836			sort.Strings(labels)
   837			fmt.Fprint(w, strings.Join(labels, ""))
   838	
   839			// Print any numeric labels for the sample
   840			var numLabels []string
   841			for key, vals := range sample.NumLabel {
   842				unit := o.NumLabelUnits[key]
   843				numValues := make([]string, len(vals))
   844				for i, vv := range vals {
   845					numValues[i] = measurement.Label(vv, unit)
   846				}
   847				numLabels = append(numLabels, fmt.Sprintf("%10s:  %s\n", key, strings.Join(numValues, " ")))
   848			}
   849			sort.Strings(numLabels)
   850			fmt.Fprint(w, strings.Join(numLabels, ""))
   851	
   852			var d, v int64
   853			v = o.SampleValue(sample.Value)
   854			if o.SampleMeanDivisor != nil {
   855				d = o.SampleMeanDivisor(sample.Value)
   856			}
   857			// Print call stack.
   858			if d != 0 {
   859				v = v / d
   860			}
   861			fmt.Fprintf(w, "%10s   %s\n",
   862				rpt.formatValue(v), stack[0].Info.PrintableName())
   863			for _, s := range stack[1:] {
   864				fmt.Fprintf(w, "%10s   %s\n", "", s.Info.PrintableName())
   865			}
   866		}
   867		fmt.Fprintln(w, separator)
   868		return nil
   869	}
   870	
   871	// printCallgrind prints a graph for a profile on callgrind format.
   872	func printCallgrind(w io.Writer, rpt *Report) error {
   873		o := rpt.options
   874		rpt.options.NodeFraction = 0
   875		rpt.options.EdgeFraction = 0
   876		rpt.options.NodeCount = 0
   877	
   878		g, _, _, _ := rpt.newTrimmedGraph()
   879		rpt.selectOutputUnit(g)
   880	
   881		nodeNames := getDisambiguatedNames(g)
   882	
   883		fmt.Fprintln(w, "positions: instr line")
   884		fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")")
   885	
   886		objfiles := make(map[string]int)
   887		files := make(map[string]int)
   888		names := make(map[string]int)
   889	
   890		// prevInfo points to the previous NodeInfo.
   891		// It is used to group cost lines together as much as possible.
   892		var prevInfo *graph.NodeInfo
   893		for _, n := range g.Nodes {
   894			if prevInfo == nil || n.Info.Objfile != prevInfo.Objfile || n.Info.File != prevInfo.File || n.Info.Name != prevInfo.Name {
   895				fmt.Fprintln(w)
   896				fmt.Fprintln(w, "ob="+callgrindName(objfiles, n.Info.Objfile))
   897				fmt.Fprintln(w, "fl="+callgrindName(files, n.Info.File))
   898				fmt.Fprintln(w, "fn="+callgrindName(names, n.Info.Name))
   899			}
   900	
   901			addr := callgrindAddress(prevInfo, n.Info.Address)
   902			sv, _ := measurement.Scale(n.FlatValue(), o.SampleUnit, o.OutputUnit)
   903			fmt.Fprintf(w, "%s %d %d\n", addr, n.Info.Lineno, int64(sv))
   904	
   905			// Print outgoing edges.
   906			for _, out := range n.Out.Sort() {
   907				c, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit)
   908				callee := out.Dest
   909				fmt.Fprintln(w, "cfl="+callgrindName(files, callee.Info.File))
   910				fmt.Fprintln(w, "cfn="+callgrindName(names, nodeNames[callee]))
   911				// pprof doesn't have a flat weight for a call, leave as 0.
   912				fmt.Fprintf(w, "calls=0 %s %d\n", callgrindAddress(prevInfo, callee.Info.Address), callee.Info.Lineno)
   913				// TODO: This address may be in the middle of a call
   914				// instruction. It would be best to find the beginning
   915				// of the instruction, but the tools seem to handle
   916				// this OK.
   917				fmt.Fprintf(w, "* * %d\n", int64(c))
   918			}
   919	
   920			prevInfo = &n.Info
   921		}
   922	
   923		return nil
   924	}
   925	
   926	// getDisambiguatedNames returns a map from each node in the graph to
   927	// the name to use in the callgrind output. Callgrind merges all
   928	// functions with the same [file name, function name]. Add a [%d/n]
   929	// suffix to disambiguate nodes with different values of
   930	// node.Function, which we want to keep separate. In particular, this
   931	// affects graphs created with --call_tree, where nodes from different
   932	// contexts are associated to different Functions.
   933	func getDisambiguatedNames(g *graph.Graph) map[*graph.Node]string {
   934		nodeName := make(map[*graph.Node]string, len(g.Nodes))
   935	
   936		type names struct {
   937			file, function string
   938		}
   939	
   940		// nameFunctionIndex maps the callgrind names (filename, function)
   941		// to the node.Function values found for that name, and each
   942		// node.Function value to a sequential index to be used on the
   943		// disambiguated name.
   944		nameFunctionIndex := make(map[names]map[*graph.Node]int)
   945		for _, n := range g.Nodes {
   946			nm := names{n.Info.File, n.Info.Name}
   947			p, ok := nameFunctionIndex[nm]
   948			if !ok {
   949				p = make(map[*graph.Node]int)
   950				nameFunctionIndex[nm] = p
   951			}
   952			if _, ok := p[n.Function]; !ok {
   953				p[n.Function] = len(p)
   954			}
   955		}
   956	
   957		for _, n := range g.Nodes {
   958			nm := names{n.Info.File, n.Info.Name}
   959			nodeName[n] = n.Info.Name
   960			if p := nameFunctionIndex[nm]; len(p) > 1 {
   961				// If there is more than one function, add suffix to disambiguate.
   962				nodeName[n] += fmt.Sprintf(" [%d/%d]", p[n.Function]+1, len(p))
   963			}
   964		}
   965		return nodeName
   966	}
   967	
   968	// callgrindName implements the callgrind naming compression scheme.
   969	// For names not previously seen returns "(N) name", where N is a
   970	// unique index. For names previously seen returns "(N)" where N is
   971	// the index returned the first time.
   972	func callgrindName(names map[string]int, name string) string {
   973		if name == "" {
   974			return ""
   975		}
   976		if id, ok := names[name]; ok {
   977			return fmt.Sprintf("(%d)", id)
   978		}
   979		id := len(names) + 1
   980		names[name] = id
   981		return fmt.Sprintf("(%d) %s", id, name)
   982	}
   983	
   984	// callgrindAddress implements the callgrind subposition compression scheme if
   985	// possible. If prevInfo != nil, it contains the previous address. The current
   986	// address can be given relative to the previous address, with an explicit +/-
   987	// to indicate it is relative, or * for the same address.
   988	func callgrindAddress(prevInfo *graph.NodeInfo, curr uint64) string {
   989		abs := fmt.Sprintf("%#x", curr)
   990		if prevInfo == nil {
   991			return abs
   992		}
   993	
   994		prev := prevInfo.Address
   995		if prev == curr {
   996			return "*"
   997		}
   998	
   999		diff := int64(curr - prev)
  1000		relative := fmt.Sprintf("%+d", diff)
  1001	
  1002		// Only bother to use the relative address if it is actually shorter.
  1003		if len(relative) < len(abs) {
  1004			return relative
  1005		}
  1006	
  1007		return abs
  1008	}
  1009	
  1010	// printTree prints a tree-based report in text form.
  1011	func printTree(w io.Writer, rpt *Report) error {
  1012		const separator = "----------------------------------------------------------+-------------"
  1013		const legend = "      flat  flat%   sum%        cum   cum%   calls calls% + context 	 	 "
  1014	
  1015		g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
  1016		rpt.selectOutputUnit(g)
  1017	
  1018		fmt.Fprintln(w, strings.Join(reportLabels(rpt, g, origCount, droppedNodes, 0, false), "\n"))
  1019	
  1020		fmt.Fprintln(w, separator)
  1021		fmt.Fprintln(w, legend)
  1022		var flatSum int64
  1023	
  1024		rx := rpt.options.Symbol
  1025		for _, n := range g.Nodes {
  1026			name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
  1027	
  1028			// Skip any entries that do not match the regexp (for the "peek" command).
  1029			if rx != nil && !rx.MatchString(name) {
  1030				continue
  1031			}
  1032	
  1033			fmt.Fprintln(w, separator)
  1034			// Print incoming edges.
  1035			inEdges := n.In.Sort()
  1036			for _, in := range inEdges {
  1037				var inline string
  1038				if in.Inline {
  1039					inline = " (inline)"
  1040				}
  1041				fmt.Fprintf(w, "%50s %s |   %s%s\n", rpt.formatValue(in.Weight),
  1042					measurement.Percentage(in.Weight, cum), in.Src.Info.PrintableName(), inline)
  1043			}
  1044	
  1045			// Print current node.
  1046			flatSum += flat
  1047			fmt.Fprintf(w, "%10s %s %s %10s %s                | %s\n",
  1048				rpt.formatValue(flat),
  1049				measurement.Percentage(flat, rpt.total),
  1050				measurement.Percentage(flatSum, rpt.total),
  1051				rpt.formatValue(cum),
  1052				measurement.Percentage(cum, rpt.total),
  1053				name)
  1054	
  1055			// Print outgoing edges.
  1056			outEdges := n.Out.Sort()
  1057			for _, out := range outEdges {
  1058				var inline string
  1059				if out.Inline {
  1060					inline = " (inline)"
  1061				}
  1062				fmt.Fprintf(w, "%50s %s |   %s%s\n", rpt.formatValue(out.Weight),
  1063					measurement.Percentage(out.Weight, cum), out.Dest.Info.PrintableName(), inline)
  1064			}
  1065		}
  1066		if len(g.Nodes) > 0 {
  1067			fmt.Fprintln(w, separator)
  1068		}
  1069		return nil
  1070	}
  1071	
  1072	// GetDOT returns a graph suitable for dot processing along with some
  1073	// configuration information.
  1074	func GetDOT(rpt *Report) (*graph.Graph, *graph.DotConfig) {
  1075		g, origCount, droppedNodes, droppedEdges := rpt.newTrimmedGraph()
  1076		rpt.selectOutputUnit(g)
  1077		labels := reportLabels(rpt, g, origCount, droppedNodes, droppedEdges, true)
  1078	
  1079		c := &graph.DotConfig{
  1080			Title:       rpt.options.Title,
  1081			Labels:      labels,
  1082			FormatValue: rpt.formatValue,
  1083			Total:       rpt.total,
  1084		}
  1085		return g, c
  1086	}
  1087	
  1088	// printDOT prints an annotated callgraph in DOT format.
  1089	func printDOT(w io.Writer, rpt *Report) error {
  1090		g, c := GetDOT(rpt)
  1091		graph.ComposeDot(w, g, &graph.DotAttributes{}, c)
  1092		return nil
  1093	}
  1094	
  1095	// ProfileLabels returns printable labels for a profile.
  1096	func ProfileLabels(rpt *Report) []string {
  1097		label := []string{}
  1098		prof := rpt.prof
  1099		o := rpt.options
  1100		if len(prof.Mapping) > 0 {
  1101			if prof.Mapping[0].File != "" {
  1102				label = append(label, "File: "+filepath.Base(prof.Mapping[0].File))
  1103			}
  1104			if prof.Mapping[0].BuildID != "" {
  1105				label = append(label, "Build ID: "+prof.Mapping[0].BuildID)
  1106			}
  1107		}
  1108		// Only include comments that do not start with '#'.
  1109		for _, c := range prof.Comments {
  1110			if !strings.HasPrefix(c, "#") {
  1111				label = append(label, c)
  1112			}
  1113		}
  1114		if o.SampleType != "" {
  1115			label = append(label, "Type: "+o.SampleType)
  1116		}
  1117		if prof.TimeNanos != 0 {
  1118			const layout = "Jan 2, 2006 at 3:04pm (MST)"
  1119			label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout))
  1120		}
  1121		if prof.DurationNanos != 0 {
  1122			duration := measurement.Label(prof.DurationNanos, "nanoseconds")
  1123			totalNanos, totalUnit := measurement.Scale(rpt.total, o.SampleUnit, "nanoseconds")
  1124			var ratio string
  1125			if totalUnit == "ns" && totalNanos != 0 {
  1126				ratio = "(" + measurement.Percentage(int64(totalNanos), prof.DurationNanos) + ")"
  1127			}
  1128			label = append(label, fmt.Sprintf("Duration: %s, Total samples = %s %s", duration, rpt.formatValue(rpt.total), ratio))
  1129		}
  1130		return label
  1131	}
  1132	
  1133	// reportLabels returns printable labels for a report. Includes
  1134	// profileLabels.
  1135	func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {
  1136		nodeFraction := rpt.options.NodeFraction
  1137		edgeFraction := rpt.options.EdgeFraction
  1138		nodeCount := len(g.Nodes)
  1139	
  1140		var label []string
  1141		if len(rpt.options.ProfileLabels) > 0 {
  1142			label = append(label, rpt.options.ProfileLabels...)
  1143		} else if fullHeaders || !rpt.options.CompactLabels {
  1144			label = ProfileLabels(rpt)
  1145		}
  1146	
  1147		var flatSum int64
  1148		for _, n := range g.Nodes {
  1149			flatSum = flatSum + n.FlatValue()
  1150		}
  1151	
  1152		if len(rpt.options.ActiveFilters) > 0 {
  1153			activeFilters := legendActiveFilters(rpt.options.ActiveFilters)
  1154			label = append(label, activeFilters...)
  1155		}
  1156	
  1157		label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(flatSum), strings.TrimSpace(measurement.Percentage(flatSum, rpt.total)), rpt.formatValue(rpt.total)))
  1158	
  1159		if rpt.total != 0 {
  1160			if droppedNodes > 0 {
  1161				label = append(label, genLabel(droppedNodes, "node", "cum",
  1162					rpt.formatValue(abs64(int64(float64(rpt.total)*nodeFraction)))))
  1163			}
  1164			if droppedEdges > 0 {
  1165				label = append(label, genLabel(droppedEdges, "edge", "freq",
  1166					rpt.formatValue(abs64(int64(float64(rpt.total)*edgeFraction)))))
  1167			}
  1168			if nodeCount > 0 && nodeCount < origCount {
  1169				label = append(label, fmt.Sprintf("Showing top %d nodes out of %d",
  1170					nodeCount, origCount))
  1171			}
  1172		}
  1173		return label
  1174	}
  1175	
  1176	func legendActiveFilters(activeFilters []string) []string {
  1177		legendActiveFilters := make([]string, len(activeFilters)+1)
  1178		legendActiveFilters[0] = "Active filters:"
  1179		for i, s := range activeFilters {
  1180			if len(s) > 80 {
  1181				s = s[:80] + "…"
  1182			}
  1183			legendActiveFilters[i+1] = "   " + s
  1184		}
  1185		return legendActiveFilters
  1186	}
  1187	
  1188	func genLabel(d int, n, l, f string) string {
  1189		if d > 1 {
  1190			n = n + "s"
  1191		}
  1192		return fmt.Sprintf("Dropped %d %s (%s <= %s)", d, n, l, f)
  1193	}
  1194	
  1195	// New builds a new report indexing the sample values interpreting the
  1196	// samples with the provided function.
  1197	func New(prof *profile.Profile, o *Options) *Report {
  1198		format := func(v int64) string {
  1199			if r := o.Ratio; r > 0 && r != 1 {
  1200				fv := float64(v) * r
  1201				v = int64(fv)
  1202			}
  1203			return measurement.ScaledLabel(v, o.SampleUnit, o.OutputUnit)
  1204		}
  1205		return &Report{prof, computeTotal(prof, o.SampleValue, o.SampleMeanDivisor),
  1206			o, format}
  1207	}
  1208	
  1209	// NewDefault builds a new report indexing the last sample value
  1210	// available.
  1211	func NewDefault(prof *profile.Profile, options Options) *Report {
  1212		index := len(prof.SampleType) - 1
  1213		o := &options
  1214		if o.Title == "" && len(prof.Mapping) > 0 && prof.Mapping[0].File != "" {
  1215			o.Title = filepath.Base(prof.Mapping[0].File)
  1216		}
  1217		o.SampleType = prof.SampleType[index].Type
  1218		o.SampleUnit = strings.ToLower(prof.SampleType[index].Unit)
  1219		o.SampleValue = func(v []int64) int64 {
  1220			return v[index]
  1221		}
  1222		return New(prof, o)
  1223	}
  1224	
  1225	// computeTotal computes the sum of the absolute value of all sample values.
  1226	// If any samples have label indicating they belong to the diff base, then the
  1227	// total will only include samples with that label.
  1228	func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) int64 {
  1229		var div, total, diffDiv, diffTotal int64
  1230		for _, sample := range prof.Sample {
  1231			var d, v int64
  1232			v = value(sample.Value)
  1233			if meanDiv != nil {
  1234				d = meanDiv(sample.Value)
  1235			}
  1236			if v < 0 {
  1237				v = -v
  1238			}
  1239			total += v
  1240			div += d
  1241			if sample.DiffBaseSample() {
  1242				diffTotal += v
  1243				diffDiv += d
  1244			}
  1245		}
  1246		if diffTotal > 0 {
  1247			total = diffTotal
  1248			div = diffDiv
  1249		}
  1250		if div != 0 {
  1251			return total / div
  1252		}
  1253		return total
  1254	}
  1255	
  1256	// Report contains the data and associated routines to extract a
  1257	// report from a profile.
  1258	type Report struct {
  1259		prof        *profile.Profile
  1260		total       int64
  1261		options     *Options
  1262		formatValue func(int64) string
  1263	}
  1264	
  1265	// Total returns the total number of samples in a report.
  1266	func (rpt *Report) Total() int64 { return rpt.total }
  1267	
  1268	func abs64(i int64) int64 {
  1269		if i < 0 {
  1270			return -i
  1271		}
  1272		return i
  1273	}
  1274	

View as plain text