github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/src/cmd/pprof/internal/report/report.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package report summarizes a performance profile into a
     6  // human-readable report.
     7  package report
     8  
     9  import (
    10  	"fmt"
    11  	"io"
    12  	"math"
    13  	"path/filepath"
    14  	"regexp"
    15  	"sort"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  
    20  	"cmd/pprof/internal/plugin"
    21  	"internal/pprof/profile"
    22  )
    23  
    24  // Generate generates a report as directed by the Report.
    25  func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
    26  	o := rpt.options
    27  
    28  	switch o.OutputFormat {
    29  	case Dot:
    30  		return printDOT(w, rpt)
    31  	case Tree:
    32  		return printTree(w, rpt)
    33  	case Text:
    34  		return printText(w, rpt)
    35  	case Raw:
    36  		fmt.Fprint(w, rpt.prof.String())
    37  		return nil
    38  	case Tags:
    39  		return printTags(w, rpt)
    40  	case Proto:
    41  		return rpt.prof.Write(w)
    42  	case Dis:
    43  		return printAssembly(w, rpt, obj)
    44  	case List:
    45  		return printSource(w, rpt)
    46  	case WebList:
    47  		return printWebSource(w, rpt, obj)
    48  	case Callgrind:
    49  		return printCallgrind(w, rpt)
    50  	}
    51  	return fmt.Errorf("unexpected output format")
    52  }
    53  
    54  // printAssembly prints an annotated assembly listing.
    55  func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
    56  	g, err := newGraph(rpt)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	o := rpt.options
    62  	prof := rpt.prof
    63  
    64  	// If the regexp source can be parsed as an address, also match
    65  	// functions that land on that address.
    66  	var address *uint64
    67  	if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
    68  		address = &hex
    69  	}
    70  
    71  	fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total))
    72  	symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)
    73  	symNodes := nodesPerSymbol(g.ns, symbols)
    74  	// Sort function names for printing.
    75  	var syms objSymbols
    76  	for s := range symNodes {
    77  		syms = append(syms, s)
    78  	}
    79  	sort.Sort(syms)
    80  
    81  	// Correlate the symbols from the binary with the profile samples.
    82  	for _, s := range syms {
    83  		sns := symNodes[s]
    84  
    85  		// Gather samples for this symbol.
    86  		flatSum, cumSum := sumNodes(sns)
    87  
    88  		// Get the function assembly.
    89  		insns, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End)
    90  		if err != nil {
    91  			return err
    92  		}
    93  
    94  		ns := annotateAssembly(insns, sns, s.base)
    95  
    96  		fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
    97  		for _, name := range s.sym.Name[1:] {
    98  			fmt.Fprintf(w, "    AKA ======================== %s\n", name)
    99  		}
   100  		fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
   101  			rpt.formatValue(flatSum), rpt.formatValue(cumSum),
   102  			percentage(cumSum, rpt.total))
   103  
   104  		for _, n := range ns {
   105  			fmt.Fprintf(w, "%10s %10s %10x: %s\n", valueOrDot(n.flat, rpt), valueOrDot(n.cum, rpt), n.info.address, n.info.name)
   106  		}
   107  	}
   108  	return nil
   109  }
   110  
   111  // symbolsFromBinaries examines the binaries listed on the profile
   112  // that have associated samples, and identifies symbols matching rx.
   113  func symbolsFromBinaries(prof *profile.Profile, g graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {
   114  	hasSamples := make(map[string]bool)
   115  	// Only examine mappings that have samples that match the
   116  	// regexp. This is an optimization to speed up pprof.
   117  	for _, n := range g.ns {
   118  		if name := n.info.prettyName(); rx.MatchString(name) && n.info.objfile != "" {
   119  			hasSamples[n.info.objfile] = true
   120  		}
   121  	}
   122  
   123  	// Walk all mappings looking for matching functions with samples.
   124  	var objSyms []*objSymbol
   125  	for _, m := range prof.Mapping {
   126  		if !hasSamples[m.File] {
   127  			if address == nil || !(m.Start <= *address && *address <= m.Limit) {
   128  				continue
   129  			}
   130  		}
   131  
   132  		f, err := obj.Open(m.File, m.Start)
   133  		if err != nil {
   134  			fmt.Printf("%v\n", err)
   135  			continue
   136  		}
   137  
   138  		// Find symbols in this binary matching the user regexp.
   139  		var addr uint64
   140  		if address != nil {
   141  			addr = *address
   142  		}
   143  		msyms, err := f.Symbols(rx, addr)
   144  		base := f.Base()
   145  		f.Close()
   146  		if err != nil {
   147  			continue
   148  		}
   149  		for _, ms := range msyms {
   150  			objSyms = append(objSyms,
   151  				&objSymbol{
   152  					sym:  ms,
   153  					base: base,
   154  				},
   155  			)
   156  		}
   157  	}
   158  
   159  	return objSyms
   160  }
   161  
   162  // objSym represents a symbol identified from a binary. It includes
   163  // the SymbolInfo from the disasm package and the base that must be
   164  // added to correspond to sample addresses
   165  type objSymbol struct {
   166  	sym  *plugin.Sym
   167  	base uint64
   168  }
   169  
   170  // objSymbols is a wrapper type to enable sorting of []*objSymbol.
   171  type objSymbols []*objSymbol
   172  
   173  func (o objSymbols) Len() int {
   174  	return len(o)
   175  }
   176  
   177  func (o objSymbols) Less(i, j int) bool {
   178  	if namei, namej := o[i].sym.Name[0], o[j].sym.Name[0]; namei != namej {
   179  		return namei < namej
   180  	}
   181  	return o[i].sym.Start < o[j].sym.Start
   182  }
   183  
   184  func (o objSymbols) Swap(i, j int) {
   185  	o[i], o[j] = o[j], o[i]
   186  }
   187  
   188  // nodesPerSymbol classifies nodes into a group of symbols.
   189  func nodesPerSymbol(ns nodes, symbols []*objSymbol) map[*objSymbol]nodes {
   190  	symNodes := make(map[*objSymbol]nodes)
   191  	for _, s := range symbols {
   192  		// Gather samples for this symbol.
   193  		for _, n := range ns {
   194  			address := n.info.address - s.base
   195  			if address >= s.sym.Start && address < s.sym.End {
   196  				symNodes[s] = append(symNodes[s], n)
   197  			}
   198  		}
   199  	}
   200  	return symNodes
   201  }
   202  
   203  // annotateAssembly annotates a set of assembly instructions with a
   204  // set of samples. It returns a set of nodes to display.  base is an
   205  // offset to adjust the sample addresses.
   206  func annotateAssembly(insns []plugin.Inst, samples nodes, base uint64) nodes {
   207  	// Add end marker to simplify printing loop.
   208  	insns = append(insns, plugin.Inst{
   209  		Addr: ^uint64(0),
   210  	})
   211  
   212  	// Ensure samples are sorted by address.
   213  	samples.sort(addressOrder)
   214  
   215  	var s int
   216  	var asm nodes
   217  	for ix, in := range insns[:len(insns)-1] {
   218  		n := node{
   219  			info: nodeInfo{
   220  				address: in.Addr,
   221  				name:    in.Text,
   222  				file:    trimPath(in.File),
   223  				lineno:  in.Line,
   224  			},
   225  		}
   226  
   227  		// Sum all the samples until the next instruction (to account
   228  		// for samples attributed to the middle of an instruction).
   229  		for next := insns[ix+1].Addr; s < len(samples) && samples[s].info.address-base < next; s++ {
   230  			n.flat += samples[s].flat
   231  			n.cum += samples[s].cum
   232  			if samples[s].info.file != "" {
   233  				n.info.file = trimPath(samples[s].info.file)
   234  				n.info.lineno = samples[s].info.lineno
   235  			}
   236  		}
   237  		asm = append(asm, &n)
   238  	}
   239  
   240  	return asm
   241  }
   242  
   243  // valueOrDot formats a value according to a report, intercepting zero
   244  // values.
   245  func valueOrDot(value int64, rpt *Report) string {
   246  	if value == 0 {
   247  		return "."
   248  	}
   249  	return rpt.formatValue(value)
   250  }
   251  
   252  // printTags collects all tags referenced in the profile and prints
   253  // them in a sorted table.
   254  func printTags(w io.Writer, rpt *Report) error {
   255  	p := rpt.prof
   256  
   257  	// Hashtable to keep accumulate tags as key,value,count.
   258  	tagMap := make(map[string]map[string]int64)
   259  	for _, s := range p.Sample {
   260  		for key, vals := range s.Label {
   261  			for _, val := range vals {
   262  				if valueMap, ok := tagMap[key]; ok {
   263  					valueMap[val] = valueMap[val] + s.Value[0]
   264  					continue
   265  				}
   266  				valueMap := make(map[string]int64)
   267  				valueMap[val] = s.Value[0]
   268  				tagMap[key] = valueMap
   269  			}
   270  		}
   271  		for key, vals := range s.NumLabel {
   272  			for _, nval := range vals {
   273  				val := scaledValueLabel(nval, key, "auto")
   274  				if valueMap, ok := tagMap[key]; ok {
   275  					valueMap[val] = valueMap[val] + s.Value[0]
   276  					continue
   277  				}
   278  				valueMap := make(map[string]int64)
   279  				valueMap[val] = s.Value[0]
   280  				tagMap[key] = valueMap
   281  			}
   282  		}
   283  	}
   284  
   285  	tagKeys := make(tags, 0, len(tagMap))
   286  	for key := range tagMap {
   287  		tagKeys = append(tagKeys, &tag{name: key})
   288  	}
   289  	sort.Sort(tagKeys)
   290  
   291  	for _, tagKey := range tagKeys {
   292  		var total int64
   293  		key := tagKey.name
   294  		tags := make(tags, 0, len(tagMap[key]))
   295  		for t, c := range tagMap[key] {
   296  			total += c
   297  			tags = append(tags, &tag{name: t, weight: c})
   298  		}
   299  
   300  		sort.Sort(tags)
   301  		fmt.Fprintf(w, "%s: Total %d\n", key, total)
   302  		for _, t := range tags {
   303  			if total > 0 {
   304  				fmt.Fprintf(w, "  %8d (%s): %s\n", t.weight,
   305  					percentage(t.weight, total), t.name)
   306  			} else {
   307  				fmt.Fprintf(w, "  %8d: %s\n", t.weight, t.name)
   308  			}
   309  		}
   310  		fmt.Fprintln(w)
   311  	}
   312  	return nil
   313  }
   314  
   315  // printText prints a flat text report for a profile.
   316  func printText(w io.Writer, rpt *Report) error {
   317  	g, err := newGraph(rpt)
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	origCount, droppedNodes, _ := g.preprocess(rpt)
   323  	fmt.Fprintln(w, strings.Join(legendDetailLabels(rpt, g, origCount, droppedNodes, 0), "\n"))
   324  
   325  	fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n",
   326  		"flat", "flat", "sum", "cum", "cum")
   327  
   328  	var flatSum int64
   329  	for _, n := range g.ns {
   330  		name, flat, cum := n.info.prettyName(), n.flat, n.cum
   331  
   332  		flatSum += flat
   333  		fmt.Fprintf(w, "%10s %s %s %10s %s  %s\n",
   334  			rpt.formatValue(flat),
   335  			percentage(flat, rpt.total),
   336  			percentage(flatSum, rpt.total),
   337  			rpt.formatValue(cum),
   338  			percentage(cum, rpt.total),
   339  			name)
   340  	}
   341  	return nil
   342  }
   343  
   344  // printCallgrind prints a graph for a profile on callgrind format.
   345  func printCallgrind(w io.Writer, rpt *Report) error {
   346  	g, err := newGraph(rpt)
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	o := rpt.options
   352  	rpt.options.NodeFraction = 0
   353  	rpt.options.EdgeFraction = 0
   354  	rpt.options.NodeCount = 0
   355  
   356  	g.preprocess(rpt)
   357  
   358  	fmt.Fprintln(w, "positions: instr line")
   359  	fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")")
   360  
   361  	objfiles := make(map[string]int)
   362  	files := make(map[string]int)
   363  	names := make(map[string]int)
   364  
   365  	// prevInfo points to the previous nodeInfo.
   366  	// It is used to group cost lines together as much as possible.
   367  	var prevInfo *nodeInfo
   368  	for _, n := range g.ns {
   369  		if prevInfo == nil || n.info.objfile != prevInfo.objfile || n.info.file != prevInfo.file || n.info.name != prevInfo.name {
   370  			fmt.Fprintln(w)
   371  			fmt.Fprintln(w, "ob="+callgrindName(objfiles, n.info.objfile))
   372  			fmt.Fprintln(w, "fl="+callgrindName(files, n.info.file))
   373  			fmt.Fprintln(w, "fn="+callgrindName(names, n.info.name))
   374  		}
   375  
   376  		addr := callgrindAddress(prevInfo, n.info.address)
   377  		sv, _ := ScaleValue(n.flat, o.SampleUnit, o.OutputUnit)
   378  		fmt.Fprintf(w, "%s %d %d\n", addr, n.info.lineno, int(sv))
   379  
   380  		// Print outgoing edges.
   381  		for _, out := range sortedEdges(n.out) {
   382  			c, _ := ScaleValue(out.weight, o.SampleUnit, o.OutputUnit)
   383  			callee := out.dest
   384  			fmt.Fprintln(w, "cfl="+callgrindName(files, callee.info.file))
   385  			fmt.Fprintln(w, "cfn="+callgrindName(names, callee.info.name))
   386  			fmt.Fprintf(w, "calls=%d %s %d\n", int(c), callgrindAddress(prevInfo, callee.info.address), callee.info.lineno)
   387  			// TODO: This address may be in the middle of a call
   388  			// instruction. It would be best to find the beginning
   389  			// of the instruction, but the tools seem to handle
   390  			// this OK.
   391  			fmt.Fprintf(w, "* * %d\n", int(c))
   392  		}
   393  
   394  		prevInfo = &n.info
   395  	}
   396  
   397  	return nil
   398  }
   399  
   400  // callgrindName implements the callgrind naming compression scheme.
   401  // For names not previously seen returns "(N) name", where N is a
   402  // unique index. For names previously seen returns "(N)" where N is
   403  // the index returned the first time.
   404  func callgrindName(names map[string]int, name string) string {
   405  	if name == "" {
   406  		return ""
   407  	}
   408  	if id, ok := names[name]; ok {
   409  		return fmt.Sprintf("(%d)", id)
   410  	}
   411  	id := len(names) + 1
   412  	names[name] = id
   413  	return fmt.Sprintf("(%d) %s", id, name)
   414  }
   415  
   416  // callgrindAddress implements the callgrind subposition compression scheme if
   417  // possible. If prevInfo != nil, it contains the previous address. The current
   418  // address can be given relative to the previous address, with an explicit +/-
   419  // to indicate it is relative, or * for the same address.
   420  func callgrindAddress(prevInfo *nodeInfo, curr uint64) string {
   421  	abs := fmt.Sprintf("%#x", curr)
   422  	if prevInfo == nil {
   423  		return abs
   424  	}
   425  
   426  	prev := prevInfo.address
   427  	if prev == curr {
   428  		return "*"
   429  	}
   430  
   431  	diff := int64(curr - prev)
   432  	relative := fmt.Sprintf("%+d", diff)
   433  
   434  	// Only bother to use the relative address if it is actually shorter.
   435  	if len(relative) < len(abs) {
   436  		return relative
   437  	}
   438  
   439  	return abs
   440  }
   441  
   442  // printTree prints a tree-based report in text form.
   443  func printTree(w io.Writer, rpt *Report) error {
   444  	const separator = "----------------------------------------------------------+-------------"
   445  	const legend = "      flat  flat%   sum%        cum   cum%   calls calls% + context 	 	 "
   446  
   447  	g, err := newGraph(rpt)
   448  	if err != nil {
   449  		return err
   450  	}
   451  
   452  	origCount, droppedNodes, _ := g.preprocess(rpt)
   453  	fmt.Fprintln(w, strings.Join(legendDetailLabels(rpt, g, origCount, droppedNodes, 0), "\n"))
   454  
   455  	fmt.Fprintln(w, separator)
   456  	fmt.Fprintln(w, legend)
   457  	var flatSum int64
   458  
   459  	rx := rpt.options.Symbol
   460  	for _, n := range g.ns {
   461  		name, flat, cum := n.info.prettyName(), n.flat, n.cum
   462  
   463  		// Skip any entries that do not match the regexp (for the "peek" command).
   464  		if rx != nil && !rx.MatchString(name) {
   465  			continue
   466  		}
   467  
   468  		fmt.Fprintln(w, separator)
   469  		// Print incoming edges.
   470  		inEdges := sortedEdges(n.in)
   471  		inSum := inEdges.sum()
   472  		for _, in := range inEdges {
   473  			fmt.Fprintf(w, "%50s %s |   %s\n", rpt.formatValue(in.weight),
   474  				percentage(in.weight, inSum), in.src.info.prettyName())
   475  		}
   476  
   477  		// Print current node.
   478  		flatSum += flat
   479  		fmt.Fprintf(w, "%10s %s %s %10s %s                | %s\n",
   480  			rpt.formatValue(flat),
   481  			percentage(flat, rpt.total),
   482  			percentage(flatSum, rpt.total),
   483  			rpt.formatValue(cum),
   484  			percentage(cum, rpt.total),
   485  			name)
   486  
   487  		// Print outgoing edges.
   488  		outEdges := sortedEdges(n.out)
   489  		outSum := outEdges.sum()
   490  		for _, out := range outEdges {
   491  			fmt.Fprintf(w, "%50s %s |   %s\n", rpt.formatValue(out.weight),
   492  				percentage(out.weight, outSum), out.dest.info.prettyName())
   493  		}
   494  	}
   495  	if len(g.ns) > 0 {
   496  		fmt.Fprintln(w, separator)
   497  	}
   498  	return nil
   499  }
   500  
   501  // printDOT prints an annotated callgraph in DOT format.
   502  func printDOT(w io.Writer, rpt *Report) error {
   503  	g, err := newGraph(rpt)
   504  	if err != nil {
   505  		return err
   506  	}
   507  
   508  	origCount, droppedNodes, droppedEdges := g.preprocess(rpt)
   509  
   510  	prof := rpt.prof
   511  	graphname := "unnamed"
   512  	if len(prof.Mapping) > 0 {
   513  		graphname = filepath.Base(prof.Mapping[0].File)
   514  	}
   515  	fmt.Fprintln(w, `digraph "`+graphname+`" {`)
   516  	fmt.Fprintln(w, `node [style=filled fillcolor="#f8f8f8"]`)
   517  	fmt.Fprintln(w, dotLegend(rpt, g, origCount, droppedNodes, droppedEdges))
   518  
   519  	if len(g.ns) == 0 {
   520  		fmt.Fprintln(w, "}")
   521  		return nil
   522  	}
   523  
   524  	// Make sure nodes have a unique consistent id.
   525  	nodeIndex := make(map[*node]int)
   526  	maxFlat := float64(g.ns[0].flat)
   527  	for i, n := range g.ns {
   528  		nodeIndex[n] = i + 1
   529  		if float64(n.flat) > maxFlat {
   530  			maxFlat = float64(n.flat)
   531  		}
   532  	}
   533  	var edges edgeList
   534  	for _, n := range g.ns {
   535  		node := dotNode(rpt, maxFlat, nodeIndex[n], n)
   536  		fmt.Fprintln(w, node)
   537  		if nodelets := dotNodelets(rpt, nodeIndex[n], n); nodelets != "" {
   538  			fmt.Fprint(w, nodelets)
   539  		}
   540  
   541  		// Collect outgoing edges.
   542  		for _, e := range n.out {
   543  			edges = append(edges, e)
   544  		}
   545  	}
   546  	// Sort edges by frequency as a hint to the graph layout engine.
   547  	sort.Sort(edges)
   548  	for _, e := range edges {
   549  		fmt.Fprintln(w, dotEdge(rpt, nodeIndex[e.src], nodeIndex[e.dest], e))
   550  	}
   551  	fmt.Fprintln(w, "}")
   552  	return nil
   553  }
   554  
   555  // percentage computes the percentage of total of a value, and encodes
   556  // it as a string. At least two digits of precision are printed.
   557  func percentage(value, total int64) string {
   558  	var ratio float64
   559  	if total != 0 {
   560  		ratio = float64(value) / float64(total) * 100
   561  	}
   562  	switch {
   563  	case ratio >= 99.95:
   564  		return "  100%"
   565  	case ratio >= 1.0:
   566  		return fmt.Sprintf("%5.2f%%", ratio)
   567  	default:
   568  		return fmt.Sprintf("%5.2g%%", ratio)
   569  	}
   570  }
   571  
   572  // dotLegend generates the overall graph label for a report in DOT format.
   573  func dotLegend(rpt *Report, g graph, origCount, droppedNodes, droppedEdges int) string {
   574  	label := legendLabels(rpt)
   575  	label = append(label, legendDetailLabels(rpt, g, origCount, droppedNodes, droppedEdges)...)
   576  	return fmt.Sprintf(`subgraph cluster_L { L [shape=box fontsize=32 label="%s\l"] }`, strings.Join(label, `\l`))
   577  }
   578  
   579  // legendLabels generates labels exclusive to graph visualization.
   580  func legendLabels(rpt *Report) []string {
   581  	prof := rpt.prof
   582  	o := rpt.options
   583  	var label []string
   584  	if len(prof.Mapping) > 0 {
   585  		if prof.Mapping[0].File != "" {
   586  			label = append(label, "File: "+filepath.Base(prof.Mapping[0].File))
   587  		}
   588  		if prof.Mapping[0].BuildID != "" {
   589  			label = append(label, "Build ID: "+prof.Mapping[0].BuildID)
   590  		}
   591  	}
   592  	if o.SampleType != "" {
   593  		label = append(label, "Type: "+o.SampleType)
   594  	}
   595  	if prof.TimeNanos != 0 {
   596  		const layout = "Jan 2, 2006 at 3:04pm (MST)"
   597  		label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout))
   598  	}
   599  	if prof.DurationNanos != 0 {
   600  		label = append(label, fmt.Sprintf("Duration: %v", time.Duration(prof.DurationNanos)))
   601  	}
   602  	return label
   603  }
   604  
   605  // legendDetailLabels generates labels common to graph and text visualization.
   606  func legendDetailLabels(rpt *Report, g graph, origCount, droppedNodes, droppedEdges int) []string {
   607  	nodeFraction := rpt.options.NodeFraction
   608  	edgeFraction := rpt.options.EdgeFraction
   609  	nodeCount := rpt.options.NodeCount
   610  
   611  	label := []string{}
   612  
   613  	var flatSum int64
   614  	for _, n := range g.ns {
   615  		flatSum = flatSum + n.flat
   616  	}
   617  
   618  	label = append(label, fmt.Sprintf("%s of %s total (%s)", rpt.formatValue(flatSum), rpt.formatValue(rpt.total), percentage(flatSum, rpt.total)))
   619  
   620  	if rpt.total > 0 {
   621  		if droppedNodes > 0 {
   622  			label = append(label, genLabel(droppedNodes, "node", "cum",
   623  				rpt.formatValue(int64(float64(rpt.total)*nodeFraction))))
   624  		}
   625  		if droppedEdges > 0 {
   626  			label = append(label, genLabel(droppedEdges, "edge", "freq",
   627  				rpt.formatValue(int64(float64(rpt.total)*edgeFraction))))
   628  		}
   629  		if nodeCount > 0 && nodeCount < origCount {
   630  			label = append(label, fmt.Sprintf("Showing top %d nodes out of %d (cum >= %s)",
   631  				nodeCount, origCount,
   632  				rpt.formatValue(g.ns[len(g.ns)-1].cum)))
   633  		}
   634  	}
   635  	return label
   636  }
   637  
   638  func genLabel(d int, n, l, f string) string {
   639  	if d > 1 {
   640  		n = n + "s"
   641  	}
   642  	return fmt.Sprintf("Dropped %d %s (%s <= %s)", d, n, l, f)
   643  }
   644  
   645  // dotNode generates a graph node in DOT format.
   646  func dotNode(rpt *Report, maxFlat float64, rIndex int, n *node) string {
   647  	flat, cum := n.flat, n.cum
   648  
   649  	labels := strings.Split(n.info.prettyName(), "::")
   650  	label := strings.Join(labels, `\n`) + `\n`
   651  
   652  	flatValue := rpt.formatValue(flat)
   653  	if flat > 0 {
   654  		label = label + fmt.Sprintf(`%s(%s)`,
   655  			flatValue,
   656  			strings.TrimSpace(percentage(flat, rpt.total)))
   657  	} else {
   658  		label = label + "0"
   659  	}
   660  	cumValue := flatValue
   661  	if cum != flat {
   662  		if flat > 0 {
   663  			label = label + `\n`
   664  		} else {
   665  			label = label + " "
   666  		}
   667  		cumValue = rpt.formatValue(cum)
   668  		label = label + fmt.Sprintf(`of %s(%s)`,
   669  			cumValue,
   670  			strings.TrimSpace(percentage(cum, rpt.total)))
   671  	}
   672  
   673  	// Scale font sizes from 8 to 24 based on percentage of flat frequency.
   674  	// Use non linear growth to emphasize the size difference.
   675  	baseFontSize, maxFontGrowth := 8, 16.0
   676  	fontSize := baseFontSize
   677  	if maxFlat > 0 && flat > 0 && float64(flat) <= maxFlat {
   678  		fontSize += int(math.Ceil(maxFontGrowth * math.Sqrt(float64(flat)/maxFlat)))
   679  	}
   680  	return fmt.Sprintf(`N%d [label="%s" fontsize=%d shape=box tooltip="%s (%s)"]`,
   681  		rIndex,
   682  		label,
   683  		fontSize, n.info.prettyName(), cumValue)
   684  }
   685  
   686  // dotEdge generates a graph edge in DOT format.
   687  func dotEdge(rpt *Report, from, to int, e *edgeInfo) string {
   688  	w := rpt.formatValue(e.weight)
   689  	attr := fmt.Sprintf(`label=" %s"`, w)
   690  	if rpt.total > 0 {
   691  		if weight := 1 + int(e.weight*100/rpt.total); weight > 1 {
   692  			attr = fmt.Sprintf(`%s weight=%d`, attr, weight)
   693  		}
   694  		if width := 1 + int(e.weight*5/rpt.total); width > 1 {
   695  			attr = fmt.Sprintf(`%s penwidth=%d`, attr, width)
   696  		}
   697  	}
   698  	arrow := "->"
   699  	if e.residual {
   700  		arrow = "..."
   701  	}
   702  	tooltip := fmt.Sprintf(`"%s %s %s (%s)"`,
   703  		e.src.info.prettyName(), arrow, e.dest.info.prettyName(), w)
   704  	attr = fmt.Sprintf(`%s tooltip=%s labeltooltip=%s`,
   705  		attr, tooltip, tooltip)
   706  
   707  	if e.residual {
   708  		attr = attr + ` style="dotted"`
   709  	}
   710  
   711  	if len(e.src.tags) > 0 {
   712  		// Separate children further if source has tags.
   713  		attr = attr + " minlen=2"
   714  	}
   715  	return fmt.Sprintf("N%d -> N%d [%s]", from, to, attr)
   716  }
   717  
   718  // dotNodelets generates the DOT boxes for the node tags.
   719  func dotNodelets(rpt *Report, rIndex int, n *node) (dot string) {
   720  	const maxNodelets = 4    // Number of nodelets for alphanumeric labels
   721  	const maxNumNodelets = 4 // Number of nodelets for numeric labels
   722  
   723  	var ts, nts tags
   724  	for _, t := range n.tags {
   725  		if t.unit == "" {
   726  			ts = append(ts, t)
   727  		} else {
   728  			nts = append(nts, t)
   729  		}
   730  	}
   731  
   732  	// Select the top maxNodelets alphanumeric labels by weight
   733  	sort.Sort(ts)
   734  	if len(ts) > maxNodelets {
   735  		ts = ts[:maxNodelets]
   736  	}
   737  	for i, t := range ts {
   738  		weight := rpt.formatValue(t.weight)
   739  		dot += fmt.Sprintf(`N%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", rIndex, i, t.name, weight)
   740  		dot += fmt.Sprintf(`N%d -> N%d_%d [label=" %s" weight=100 tooltip="\L" labeltooltip="\L"]`+"\n", rIndex, rIndex, i, weight)
   741  	}
   742  
   743  	// Collapse numeric labels into maxNumNodelets buckets, of the form:
   744  	// 1MB..2MB, 3MB..5MB, ...
   745  	nts = collapseTags(nts, maxNumNodelets)
   746  	sort.Sort(nts)
   747  	for i, t := range nts {
   748  		weight := rpt.formatValue(t.weight)
   749  		dot += fmt.Sprintf(`NN%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", rIndex, i, t.name, weight)
   750  		dot += fmt.Sprintf(`N%d -> NN%d_%d [label=" %s" weight=100 tooltip="\L" labeltooltip="\L"]`+"\n", rIndex, rIndex, i, weight)
   751  	}
   752  
   753  	return dot
   754  }
   755  
   756  // graph summarizes a performance profile into a format that is
   757  // suitable for visualization.
   758  type graph struct {
   759  	ns nodes
   760  }
   761  
   762  // nodes is an ordered collection of graph nodes.
   763  type nodes []*node
   764  
   765  // tags represent sample annotations
   766  type tags []*tag
   767  type tagMap map[string]*tag
   768  
   769  type tag struct {
   770  	name   string
   771  	unit   string // Describe the value, "" for non-numeric tags
   772  	value  int64
   773  	weight int64
   774  }
   775  
   776  func (t tags) Len() int      { return len(t) }
   777  func (t tags) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
   778  func (t tags) Less(i, j int) bool {
   779  	if t[i].weight == t[j].weight {
   780  		return t[i].name < t[j].name
   781  	}
   782  	return t[i].weight > t[j].weight
   783  }
   784  
   785  // node is an entry on a profiling report. It represents a unique
   786  // program location. It can include multiple names to represent
   787  // inlined functions.
   788  type node struct {
   789  	info nodeInfo // Information associated to this entry.
   790  
   791  	// values associated to this node.
   792  	// flat is exclusive to this node, cum includes all descendents.
   793  	flat, cum int64
   794  
   795  	// in and out contains the nodes immediately reaching or reached by this nodes.
   796  	in, out edgeMap
   797  
   798  	// tags provide additional information about subsets of a sample.
   799  	tags tagMap
   800  }
   801  
   802  type nodeInfo struct {
   803  	name              string
   804  	origName          string
   805  	address           uint64
   806  	file              string
   807  	startLine, lineno int
   808  	inline            bool
   809  	lowPriority       bool
   810  	objfile           string
   811  	parent            *node // Used only if creating a calltree
   812  }
   813  
   814  func (n *node) addTags(s *profile.Sample, weight int64) {
   815  	// Add a tag with all string labels
   816  	var labels []string
   817  	for key, vals := range s.Label {
   818  		for _, v := range vals {
   819  			labels = append(labels, key+":"+v)
   820  		}
   821  	}
   822  	if len(labels) > 0 {
   823  		sort.Strings(labels)
   824  		l := n.tags.findOrAddTag(strings.Join(labels, `\n`), "", 0)
   825  		l.weight += weight
   826  	}
   827  
   828  	for key, nvals := range s.NumLabel {
   829  		for _, v := range nvals {
   830  			label := scaledValueLabel(v, key, "auto")
   831  			l := n.tags.findOrAddTag(label, key, v)
   832  			l.weight += weight
   833  		}
   834  	}
   835  }
   836  
   837  func (m tagMap) findOrAddTag(label, unit string, value int64) *tag {
   838  	if l := m[label]; l != nil {
   839  		return l
   840  	}
   841  	l := &tag{
   842  		name:  label,
   843  		unit:  unit,
   844  		value: value,
   845  	}
   846  	m[label] = l
   847  	return l
   848  }
   849  
   850  // collapseTags reduces the number of entries in a tagMap by merging
   851  // adjacent nodes into ranges. It uses a greedy approach to merge
   852  // starting with the entries with the lowest weight.
   853  func collapseTags(ts tags, count int) tags {
   854  	if len(ts) <= count {
   855  		return ts
   856  	}
   857  
   858  	sort.Sort(ts)
   859  	tagGroups := make([]tags, count)
   860  	for i, t := range ts[:count] {
   861  		tagGroups[i] = tags{t}
   862  	}
   863  	for _, t := range ts[count:] {
   864  		g, d := 0, tagDistance(t, tagGroups[0][0])
   865  		for i := 1; i < count; i++ {
   866  			if nd := tagDistance(t, tagGroups[i][0]); nd < d {
   867  				g, d = i, nd
   868  			}
   869  		}
   870  		tagGroups[g] = append(tagGroups[g], t)
   871  	}
   872  
   873  	var nts tags
   874  	for _, g := range tagGroups {
   875  		l, w := tagGroupLabel(g)
   876  		nts = append(nts, &tag{
   877  			name:   l,
   878  			weight: w,
   879  		})
   880  	}
   881  	return nts
   882  }
   883  
   884  func tagDistance(t, u *tag) float64 {
   885  	v, _ := ScaleValue(u.value, u.unit, t.unit)
   886  	if v < float64(t.value) {
   887  		return float64(t.value) - v
   888  	}
   889  	return v - float64(t.value)
   890  }
   891  
   892  func tagGroupLabel(g tags) (string, int64) {
   893  	if len(g) == 1 {
   894  		t := g[0]
   895  		return scaledValueLabel(t.value, t.unit, "auto"), t.weight
   896  	}
   897  	min := g[0]
   898  	max := g[0]
   899  	w := min.weight
   900  	for _, t := range g[1:] {
   901  		if v, _ := ScaleValue(t.value, t.unit, min.unit); int64(v) < min.value {
   902  			min = t
   903  		}
   904  		if v, _ := ScaleValue(t.value, t.unit, max.unit); int64(v) > max.value {
   905  			max = t
   906  		}
   907  		w += t.weight
   908  	}
   909  	return scaledValueLabel(min.value, min.unit, "auto") + ".." +
   910  		scaledValueLabel(max.value, max.unit, "auto"), w
   911  }
   912  
   913  // sumNodes adds the flat and sum values on a report.
   914  func sumNodes(ns nodes) (flat int64, cum int64) {
   915  	for _, n := range ns {
   916  		flat += n.flat
   917  		cum += n.cum
   918  	}
   919  	return
   920  }
   921  
   922  type edgeMap map[*node]*edgeInfo
   923  
   924  // edgeInfo contains any attributes to be represented about edges in a graph/
   925  type edgeInfo struct {
   926  	src, dest *node
   927  	// The summary weight of the edge
   928  	weight int64
   929  	// residual edges connect nodes that were connected through a
   930  	// separate node, which has been removed from the report.
   931  	residual bool
   932  }
   933  
   934  // bumpWeight increases the weight of an edge. If there isn't such an
   935  // edge in the map one is created.
   936  func bumpWeight(from, to *node, w int64, residual bool) {
   937  	if from.out[to] != to.in[from] {
   938  		panic(fmt.Errorf("asymmetric edges %v %v", *from, *to))
   939  	}
   940  
   941  	if n := from.out[to]; n != nil {
   942  		n.weight += w
   943  		if n.residual && !residual {
   944  			n.residual = false
   945  		}
   946  		return
   947  	}
   948  
   949  	info := &edgeInfo{src: from, dest: to, weight: w, residual: residual}
   950  	from.out[to] = info
   951  	to.in[from] = info
   952  }
   953  
   954  // Output formats.
   955  const (
   956  	Proto = iota
   957  	Dot
   958  	Tags
   959  	Tree
   960  	Text
   961  	Raw
   962  	Dis
   963  	List
   964  	WebList
   965  	Callgrind
   966  )
   967  
   968  // Options are the formatting and filtering options used to generate a
   969  // profile.
   970  type Options struct {
   971  	OutputFormat int
   972  
   973  	CumSort        bool
   974  	CallTree       bool
   975  	PrintAddresses bool
   976  	DropNegative   bool
   977  	Ratio          float64
   978  
   979  	NodeCount    int
   980  	NodeFraction float64
   981  	EdgeFraction float64
   982  
   983  	SampleType string
   984  	SampleUnit string // Unit for the sample data from the profile.
   985  	OutputUnit string // Units for data formatting in report.
   986  
   987  	Symbol *regexp.Regexp // Symbols to include on disassembly report.
   988  }
   989  
   990  // newGraph summarizes performance data from a profile into a graph.
   991  func newGraph(rpt *Report) (g graph, err error) {
   992  	prof := rpt.prof
   993  	o := rpt.options
   994  
   995  	// Generate a tree for graphical output if requested.
   996  	buildTree := o.CallTree && o.OutputFormat == Dot
   997  
   998  	locations := make(map[uint64][]nodeInfo)
   999  	for _, l := range prof.Location {
  1000  		locations[l.ID] = newLocInfo(l)
  1001  	}
  1002  
  1003  	nm := make(nodeMap)
  1004  	for _, sample := range prof.Sample {
  1005  		if sample.Location == nil {
  1006  			continue
  1007  		}
  1008  
  1009  		// Construct list of node names for sample.
  1010  		var stack []nodeInfo
  1011  		for _, loc := range sample.Location {
  1012  			id := loc.ID
  1013  			stack = append(stack, locations[id]...)
  1014  		}
  1015  
  1016  		// Upfront pass to update the parent chains, to prevent the
  1017  		// merging of nodes with different parents.
  1018  		if buildTree {
  1019  			var nn *node
  1020  			for i := len(stack); i > 0; i-- {
  1021  				n := &stack[i-1]
  1022  				n.parent = nn
  1023  				nn = nm.findOrInsertNode(*n)
  1024  			}
  1025  		}
  1026  
  1027  		leaf := nm.findOrInsertNode(stack[0])
  1028  		weight := rpt.sampleValue(sample)
  1029  		leaf.addTags(sample, weight)
  1030  
  1031  		// Aggregate counter data.
  1032  		leaf.flat += weight
  1033  		seen := make(map[*node]bool)
  1034  		var nn *node
  1035  		for _, s := range stack {
  1036  			n := nm.findOrInsertNode(s)
  1037  			if !seen[n] {
  1038  				seen[n] = true
  1039  				n.cum += weight
  1040  
  1041  				if nn != nil {
  1042  					bumpWeight(n, nn, weight, false)
  1043  				}
  1044  			}
  1045  			nn = n
  1046  		}
  1047  	}
  1048  
  1049  	// Collect new nodes into a report.
  1050  	ns := make(nodes, 0, len(nm))
  1051  	for _, n := range nm {
  1052  		if rpt.options.DropNegative && n.flat < 0 {
  1053  			continue
  1054  		}
  1055  		ns = append(ns, n)
  1056  	}
  1057  
  1058  	return graph{ns}, nil
  1059  }
  1060  
  1061  // Create a slice of formatted names for a location.
  1062  func newLocInfo(l *profile.Location) []nodeInfo {
  1063  	var objfile string
  1064  
  1065  	if m := l.Mapping; m != nil {
  1066  		objfile = m.File
  1067  	}
  1068  
  1069  	if len(l.Line) == 0 {
  1070  		return []nodeInfo{
  1071  			{
  1072  				address: l.Address,
  1073  				objfile: objfile,
  1074  			},
  1075  		}
  1076  	}
  1077  	var info []nodeInfo
  1078  	numInlineFrames := len(l.Line) - 1
  1079  	for li, line := range l.Line {
  1080  		ni := nodeInfo{
  1081  			address: l.Address,
  1082  			lineno:  int(line.Line),
  1083  			inline:  li < numInlineFrames,
  1084  			objfile: objfile,
  1085  		}
  1086  
  1087  		if line.Function != nil {
  1088  			ni.name = line.Function.Name
  1089  			ni.origName = line.Function.SystemName
  1090  			ni.file = line.Function.Filename
  1091  			ni.startLine = int(line.Function.StartLine)
  1092  		}
  1093  
  1094  		info = append(info, ni)
  1095  	}
  1096  	return info
  1097  }
  1098  
  1099  // nodeMap maps from a node info struct to a node. It is used to merge
  1100  // report entries with the same info.
  1101  type nodeMap map[nodeInfo]*node
  1102  
  1103  func (m nodeMap) findOrInsertNode(info nodeInfo) *node {
  1104  	rr := m[info]
  1105  	if rr == nil {
  1106  		rr = &node{
  1107  			info: info,
  1108  			in:   make(edgeMap),
  1109  			out:  make(edgeMap),
  1110  			tags: make(map[string]*tag),
  1111  		}
  1112  		m[info] = rr
  1113  	}
  1114  	return rr
  1115  }
  1116  
  1117  // preprocess does any required filtering/sorting according to the
  1118  // report options. Returns the mapping from each node to any nodes
  1119  // removed by path compression and statistics on the nodes/edges removed.
  1120  func (g *graph) preprocess(rpt *Report) (origCount, droppedNodes, droppedEdges int) {
  1121  	o := rpt.options
  1122  
  1123  	// Compute total weight of current set of nodes.
  1124  	// This is <= rpt.total because of node filtering.
  1125  	var totalValue int64
  1126  	for _, n := range g.ns {
  1127  		totalValue += n.flat
  1128  	}
  1129  
  1130  	// Remove nodes with value <= total*nodeFraction
  1131  	if nodeFraction := o.NodeFraction; nodeFraction > 0 {
  1132  		var removed nodes
  1133  		minValue := int64(float64(totalValue) * nodeFraction)
  1134  		kept := make(nodes, 0, len(g.ns))
  1135  		for _, n := range g.ns {
  1136  			if n.cum < minValue {
  1137  				removed = append(removed, n)
  1138  			} else {
  1139  				kept = append(kept, n)
  1140  				tagsKept := make(map[string]*tag)
  1141  				for s, t := range n.tags {
  1142  					if t.weight >= minValue {
  1143  						tagsKept[s] = t
  1144  					}
  1145  				}
  1146  				n.tags = tagsKept
  1147  			}
  1148  		}
  1149  		droppedNodes = len(removed)
  1150  		removeNodes(removed, false, false)
  1151  		g.ns = kept
  1152  	}
  1153  
  1154  	// Remove edges below minimum frequency.
  1155  	if edgeFraction := o.EdgeFraction; edgeFraction > 0 {
  1156  		minEdge := int64(float64(totalValue) * edgeFraction)
  1157  		for _, n := range g.ns {
  1158  			for src, e := range n.in {
  1159  				if e.weight < minEdge {
  1160  					delete(n.in, src)
  1161  					delete(src.out, n)
  1162  					droppedEdges++
  1163  				}
  1164  			}
  1165  		}
  1166  	}
  1167  
  1168  	sortOrder := flatName
  1169  	if o.CumSort {
  1170  		// Force cum sorting for graph output, to preserve connectivity.
  1171  		sortOrder = cumName
  1172  	}
  1173  
  1174  	// Nodes that have flat==0 and a single in/out do not provide much
  1175  	// information. Give them first chance to be removed. Do not consider edges
  1176  	// from/to nodes that are expected to be removed.
  1177  	maxNodes := o.NodeCount
  1178  	if o.OutputFormat == Dot {
  1179  		if maxNodes > 0 && maxNodes < len(g.ns) {
  1180  			sortOrder = cumName
  1181  			g.ns.sort(cumName)
  1182  			cumCutoff := g.ns[maxNodes].cum
  1183  			for _, n := range g.ns {
  1184  				if n.flat == 0 {
  1185  					if count := countEdges(n.out, cumCutoff); count > 1 {
  1186  						continue
  1187  					}
  1188  					if count := countEdges(n.in, cumCutoff); count != 1 {
  1189  						continue
  1190  					}
  1191  					n.info.lowPriority = true
  1192  				}
  1193  			}
  1194  		}
  1195  	}
  1196  
  1197  	g.ns.sort(sortOrder)
  1198  	if maxNodes > 0 {
  1199  		origCount = len(g.ns)
  1200  		for index, nodes := 0, 0; index < len(g.ns); index++ {
  1201  			nodes++
  1202  			// For DOT output, count the tags as nodes since we will draw
  1203  			// boxes for them.
  1204  			if o.OutputFormat == Dot {
  1205  				nodes += len(g.ns[index].tags)
  1206  			}
  1207  			if nodes > maxNodes {
  1208  				// Trim to the top n nodes. Create dotted edges to bridge any
  1209  				// broken connections.
  1210  				removeNodes(g.ns[index:], true, true)
  1211  				g.ns = g.ns[:index]
  1212  				break
  1213  			}
  1214  		}
  1215  	}
  1216  	removeRedundantEdges(g.ns)
  1217  
  1218  	// Select best unit for profile output.
  1219  	// Find the appropriate units for the smallest non-zero sample
  1220  	if o.OutputUnit == "minimum" && len(g.ns) > 0 {
  1221  		var maxValue, minValue int64
  1222  
  1223  		for _, n := range g.ns {
  1224  			if n.flat > 0 && (minValue == 0 || n.flat < minValue) {
  1225  				minValue = n.flat
  1226  			}
  1227  			if n.cum > maxValue {
  1228  				maxValue = n.cum
  1229  			}
  1230  		}
  1231  		if r := o.Ratio; r > 0 && r != 1 {
  1232  			minValue = int64(float64(minValue) * r)
  1233  			maxValue = int64(float64(maxValue) * r)
  1234  		}
  1235  
  1236  		_, minUnit := ScaleValue(minValue, o.SampleUnit, "minimum")
  1237  		_, maxUnit := ScaleValue(maxValue, o.SampleUnit, "minimum")
  1238  
  1239  		unit := minUnit
  1240  		if minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind {
  1241  			// Minimum and maximum values have different units. Scale
  1242  			// minimum by 100 to use larger units, allowing minimum value to
  1243  			// be scaled down to 0.01, except for callgrind reports since
  1244  			// they can only represent integer values.
  1245  			_, unit = ScaleValue(100*minValue, o.SampleUnit, "minimum")
  1246  		}
  1247  
  1248  		if unit != "" {
  1249  			o.OutputUnit = unit
  1250  		} else {
  1251  			o.OutputUnit = o.SampleUnit
  1252  		}
  1253  	}
  1254  	return
  1255  }
  1256  
  1257  // countEdges counts the number of edges below the specified cutoff.
  1258  func countEdges(el edgeMap, cutoff int64) int {
  1259  	count := 0
  1260  	for _, e := range el {
  1261  		if e.weight > cutoff {
  1262  			count++
  1263  		}
  1264  	}
  1265  	return count
  1266  }
  1267  
  1268  // removeNodes removes nodes from a report, optionally bridging
  1269  // connections between in/out edges and spreading out their weights
  1270  // proportionally. residual marks new bridge edges as residual
  1271  // (dotted).
  1272  func removeNodes(toRemove nodes, bridge, residual bool) {
  1273  	for _, n := range toRemove {
  1274  		for ei := range n.in {
  1275  			delete(ei.out, n)
  1276  		}
  1277  		if bridge {
  1278  			for ei, wi := range n.in {
  1279  				for eo, wo := range n.out {
  1280  					var weight int64
  1281  					if n.cum != 0 {
  1282  						weight = int64(float64(wo.weight) * (float64(wi.weight) / float64(n.cum)))
  1283  					}
  1284  					bumpWeight(ei, eo, weight, residual)
  1285  				}
  1286  			}
  1287  		}
  1288  		for eo := range n.out {
  1289  			delete(eo.in, n)
  1290  		}
  1291  	}
  1292  }
  1293  
  1294  // removeRedundantEdges removes residual edges if the destination can
  1295  // be reached through another path. This is done to simplify the graph
  1296  // while preserving connectivity.
  1297  func removeRedundantEdges(ns nodes) {
  1298  	// Walk the nodes and outgoing edges in reverse order to prefer
  1299  	// removing edges with the lowest weight.
  1300  	for i := len(ns); i > 0; i-- {
  1301  		n := ns[i-1]
  1302  		in := sortedEdges(n.in)
  1303  		for j := len(in); j > 0; j-- {
  1304  			if e := in[j-1]; e.residual && isRedundant(e) {
  1305  				delete(e.src.out, e.dest)
  1306  				delete(e.dest.in, e.src)
  1307  			}
  1308  		}
  1309  	}
  1310  }
  1311  
  1312  // isRedundant determines if an edge can be removed without impacting
  1313  // connectivity of the whole graph. This is implemented by checking if the
  1314  // nodes have a common ancestor after removing the edge.
  1315  func isRedundant(e *edgeInfo) bool {
  1316  	destPred := predecessors(e, e.dest)
  1317  	if len(destPred) == 1 {
  1318  		return false
  1319  	}
  1320  	srcPred := predecessors(e, e.src)
  1321  
  1322  	for n := range srcPred {
  1323  		if destPred[n] && n != e.dest {
  1324  			return true
  1325  		}
  1326  	}
  1327  	return false
  1328  }
  1329  
  1330  // predecessors collects all the predecessors to node n, excluding edge e.
  1331  func predecessors(e *edgeInfo, n *node) map[*node]bool {
  1332  	seen := map[*node]bool{n: true}
  1333  	queue := []*node{n}
  1334  	for len(queue) > 0 {
  1335  		n := queue[0]
  1336  		queue = queue[1:]
  1337  		for _, ie := range n.in {
  1338  			if e == ie || seen[ie.src] {
  1339  				continue
  1340  			}
  1341  			seen[ie.src] = true
  1342  			queue = append(queue, ie.src)
  1343  		}
  1344  	}
  1345  	return seen
  1346  }
  1347  
  1348  // nodeSorter is a mechanism used to allow a report to be sorted
  1349  // in different ways.
  1350  type nodeSorter struct {
  1351  	rs   nodes
  1352  	less func(i, j int) bool
  1353  }
  1354  
  1355  func (s nodeSorter) Len() int           { return len(s.rs) }
  1356  func (s nodeSorter) Swap(i, j int)      { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] }
  1357  func (s nodeSorter) Less(i, j int) bool { return s.less(i, j) }
  1358  
  1359  type nodeOrder int
  1360  
  1361  const (
  1362  	flatName nodeOrder = iota
  1363  	flatCumName
  1364  	cumName
  1365  	nameOrder
  1366  	fileOrder
  1367  	addressOrder
  1368  )
  1369  
  1370  // sort reorders the entries in a report based on the specified
  1371  // ordering criteria. The result is sorted in decreasing order for
  1372  // numeric quantities, alphabetically for text, and increasing for
  1373  // addresses.
  1374  func (ns nodes) sort(o nodeOrder) error {
  1375  	var s nodeSorter
  1376  
  1377  	switch o {
  1378  	case flatName:
  1379  		s = nodeSorter{ns,
  1380  			func(i, j int) bool {
  1381  				if iv, jv := ns[i].flat, ns[j].flat; iv != jv {
  1382  					return iv > jv
  1383  				}
  1384  				if ns[i].info.prettyName() != ns[j].info.prettyName() {
  1385  					return ns[i].info.prettyName() < ns[j].info.prettyName()
  1386  				}
  1387  				iv, jv := ns[i].cum, ns[j].cum
  1388  				return iv > jv
  1389  			},
  1390  		}
  1391  	case flatCumName:
  1392  		s = nodeSorter{ns,
  1393  			func(i, j int) bool {
  1394  				if iv, jv := ns[i].flat, ns[j].flat; iv != jv {
  1395  					return iv > jv
  1396  				}
  1397  				if iv, jv := ns[i].cum, ns[j].cum; iv != jv {
  1398  					return iv > jv
  1399  				}
  1400  				return ns[i].info.prettyName() < ns[j].info.prettyName()
  1401  			},
  1402  		}
  1403  	case cumName:
  1404  		s = nodeSorter{ns,
  1405  			func(i, j int) bool {
  1406  				if ns[i].info.lowPriority != ns[j].info.lowPriority {
  1407  					return ns[j].info.lowPriority
  1408  				}
  1409  				if iv, jv := ns[i].cum, ns[j].cum; iv != jv {
  1410  					return iv > jv
  1411  				}
  1412  				if ns[i].info.prettyName() != ns[j].info.prettyName() {
  1413  					return ns[i].info.prettyName() < ns[j].info.prettyName()
  1414  				}
  1415  				iv, jv := ns[i].flat, ns[j].flat
  1416  				return iv > jv
  1417  			},
  1418  		}
  1419  	case nameOrder:
  1420  		s = nodeSorter{ns,
  1421  			func(i, j int) bool {
  1422  				return ns[i].info.name < ns[j].info.name
  1423  			},
  1424  		}
  1425  	case fileOrder:
  1426  		s = nodeSorter{ns,
  1427  			func(i, j int) bool {
  1428  				return ns[i].info.file < ns[j].info.file
  1429  			},
  1430  		}
  1431  	case addressOrder:
  1432  		s = nodeSorter{ns,
  1433  			func(i, j int) bool {
  1434  				return ns[i].info.address < ns[j].info.address
  1435  			},
  1436  		}
  1437  	default:
  1438  		return fmt.Errorf("report: unrecognized sort ordering: %d", o)
  1439  	}
  1440  	sort.Sort(s)
  1441  	return nil
  1442  }
  1443  
  1444  type edgeList []*edgeInfo
  1445  
  1446  // sortedEdges return a slice of the edges in the map, sorted for
  1447  // visualization. The sort order is first based on the edge weight
  1448  // (higher-to-lower) and then by the node names to avoid flakiness.
  1449  func sortedEdges(edges map[*node]*edgeInfo) edgeList {
  1450  	el := make(edgeList, 0, len(edges))
  1451  	for _, w := range edges {
  1452  		el = append(el, w)
  1453  	}
  1454  
  1455  	sort.Sort(el)
  1456  	return el
  1457  }
  1458  
  1459  func (el edgeList) Len() int {
  1460  	return len(el)
  1461  }
  1462  
  1463  func (el edgeList) Less(i, j int) bool {
  1464  	if el[i].weight != el[j].weight {
  1465  		return el[i].weight > el[j].weight
  1466  	}
  1467  
  1468  	from1 := el[i].src.info.prettyName()
  1469  	from2 := el[j].src.info.prettyName()
  1470  	if from1 != from2 {
  1471  		return from1 < from2
  1472  	}
  1473  
  1474  	to1 := el[i].dest.info.prettyName()
  1475  	to2 := el[j].dest.info.prettyName()
  1476  
  1477  	return to1 < to2
  1478  }
  1479  
  1480  func (el edgeList) Swap(i, j int) {
  1481  	el[i], el[j] = el[j], el[i]
  1482  }
  1483  
  1484  func (el edgeList) sum() int64 {
  1485  	var ret int64
  1486  	for _, e := range el {
  1487  		ret += e.weight
  1488  	}
  1489  	return ret
  1490  }
  1491  
  1492  // ScaleValue reformats a value from a unit to a different unit.
  1493  func ScaleValue(value int64, fromUnit, toUnit string) (sv float64, su string) {
  1494  	// Avoid infinite recursion on overflow.
  1495  	if value < 0 && -value > 0 {
  1496  		v, u := ScaleValue(-value, fromUnit, toUnit)
  1497  		return -v, u
  1498  	}
  1499  	if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok {
  1500  		return m, u
  1501  	}
  1502  	if t, u, ok := timeLabel(value, fromUnit, toUnit); ok {
  1503  		return t, u
  1504  	}
  1505  	// Skip non-interesting units.
  1506  	switch toUnit {
  1507  	case "count", "sample", "unit", "minimum":
  1508  		return float64(value), ""
  1509  	default:
  1510  		return float64(value), toUnit
  1511  	}
  1512  }
  1513  
  1514  func scaledValueLabel(value int64, fromUnit, toUnit string) string {
  1515  	v, u := ScaleValue(value, fromUnit, toUnit)
  1516  
  1517  	sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
  1518  	if sv == "0" || sv == "-0" {
  1519  		return "0"
  1520  	}
  1521  	return sv + u
  1522  }
  1523  
  1524  func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
  1525  	fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s")
  1526  	toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s")
  1527  
  1528  	switch fromUnit {
  1529  	case "byte", "b":
  1530  	case "kilobyte", "kb":
  1531  		value *= 1024
  1532  	case "megabyte", "mb":
  1533  		value *= 1024 * 1024
  1534  	case "gigabyte", "gb":
  1535  		value *= 1024 * 1024 * 1024
  1536  	default:
  1537  		return 0, "", false
  1538  	}
  1539  
  1540  	if toUnit == "minimum" || toUnit == "auto" {
  1541  		switch {
  1542  		case value < 1024:
  1543  			toUnit = "b"
  1544  		case value < 1024*1024:
  1545  			toUnit = "kb"
  1546  		case value < 1024*1024*1024:
  1547  			toUnit = "mb"
  1548  		default:
  1549  			toUnit = "gb"
  1550  		}
  1551  	}
  1552  
  1553  	var output float64
  1554  	switch toUnit {
  1555  	default:
  1556  		output, toUnit = float64(value), "B"
  1557  	case "kb", "kbyte", "kilobyte":
  1558  		output, toUnit = float64(value)/1024, "kB"
  1559  	case "mb", "mbyte", "megabyte":
  1560  		output, toUnit = float64(value)/(1024*1024), "MB"
  1561  	case "gb", "gbyte", "gigabyte":
  1562  		output, toUnit = float64(value)/(1024*1024*1024), "GB"
  1563  	}
  1564  	return output, toUnit, true
  1565  }
  1566  
  1567  func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
  1568  	fromUnit = strings.ToLower(fromUnit)
  1569  	if len(fromUnit) > 2 {
  1570  		fromUnit = strings.TrimSuffix(fromUnit, "s")
  1571  	}
  1572  
  1573  	toUnit = strings.ToLower(toUnit)
  1574  	if len(toUnit) > 2 {
  1575  		toUnit = strings.TrimSuffix(toUnit, "s")
  1576  	}
  1577  
  1578  	var d time.Duration
  1579  	switch fromUnit {
  1580  	case "nanosecond", "ns":
  1581  		d = time.Duration(value) * time.Nanosecond
  1582  	case "microsecond":
  1583  		d = time.Duration(value) * time.Microsecond
  1584  	case "millisecond", "ms":
  1585  		d = time.Duration(value) * time.Millisecond
  1586  	case "second", "sec":
  1587  		d = time.Duration(value) * time.Second
  1588  	case "cycle":
  1589  		return float64(value), "", true
  1590  	default:
  1591  		return 0, "", false
  1592  	}
  1593  
  1594  	if toUnit == "minimum" || toUnit == "auto" {
  1595  		switch {
  1596  		case d < 1*time.Microsecond:
  1597  			toUnit = "ns"
  1598  		case d < 1*time.Millisecond:
  1599  			toUnit = "us"
  1600  		case d < 1*time.Second:
  1601  			toUnit = "ms"
  1602  		case d < 1*time.Minute:
  1603  			toUnit = "sec"
  1604  		case d < 1*time.Hour:
  1605  			toUnit = "min"
  1606  		case d < 24*time.Hour:
  1607  			toUnit = "hour"
  1608  		case d < 15*24*time.Hour:
  1609  			toUnit = "day"
  1610  		case d < 120*24*time.Hour:
  1611  			toUnit = "week"
  1612  		default:
  1613  			toUnit = "year"
  1614  		}
  1615  	}
  1616  
  1617  	var output float64
  1618  	dd := float64(d)
  1619  	switch toUnit {
  1620  	case "ns", "nanosecond":
  1621  		output, toUnit = dd/float64(time.Nanosecond), "ns"
  1622  	case "us", "microsecond":
  1623  		output, toUnit = dd/float64(time.Microsecond), "us"
  1624  	case "ms", "millisecond":
  1625  		output, toUnit = dd/float64(time.Millisecond), "ms"
  1626  	case "min", "minute":
  1627  		output, toUnit = dd/float64(time.Minute), "mins"
  1628  	case "hour", "hr":
  1629  		output, toUnit = dd/float64(time.Hour), "hrs"
  1630  	case "day":
  1631  		output, toUnit = dd/float64(24*time.Hour), "days"
  1632  	case "week", "wk":
  1633  		output, toUnit = dd/float64(7*24*time.Hour), "wks"
  1634  	case "year", "yr":
  1635  		output, toUnit = dd/float64(365*7*24*time.Hour), "yrs"
  1636  	default:
  1637  		fallthrough
  1638  	case "sec", "second", "s":
  1639  		output, toUnit = dd/float64(time.Second), "s"
  1640  	}
  1641  	return output, toUnit, true
  1642  }
  1643  
  1644  // prettyName determines the printable name to be used for a node.
  1645  func (info *nodeInfo) prettyName() string {
  1646  	var name string
  1647  	if info.address != 0 {
  1648  		name = fmt.Sprintf("%016x", info.address)
  1649  	}
  1650  
  1651  	if info.name != "" {
  1652  		name = name + " " + info.name
  1653  	}
  1654  
  1655  	if info.file != "" {
  1656  		name += " " + trimPath(info.file)
  1657  		if info.lineno != 0 {
  1658  			name += fmt.Sprintf(":%d", info.lineno)
  1659  		}
  1660  	}
  1661  
  1662  	if info.inline {
  1663  		name = name + " (inline)"
  1664  	}
  1665  
  1666  	if name = strings.TrimSpace(name); name == "" && info.objfile != "" {
  1667  		name = "[" + filepath.Base(info.objfile) + "]"
  1668  	}
  1669  	return name
  1670  }
  1671  
  1672  // New builds a new report indexing the sample values interpreting the
  1673  // samples with the provided function.
  1674  func New(prof *profile.Profile, options Options, value func(s *profile.Sample) int64, unit string) *Report {
  1675  	o := &options
  1676  	if o.SampleUnit == "" {
  1677  		o.SampleUnit = unit
  1678  	}
  1679  	format := func(v int64) string {
  1680  		if r := o.Ratio; r > 0 && r != 1 {
  1681  			fv := float64(v) * r
  1682  			v = int64(fv)
  1683  		}
  1684  		return scaledValueLabel(v, o.SampleUnit, o.OutputUnit)
  1685  	}
  1686  	return &Report{prof, computeTotal(prof, value), o, value, format}
  1687  }
  1688  
  1689  // NewDefault builds a new report indexing the sample values with the
  1690  // last value available.
  1691  func NewDefault(prof *profile.Profile, options Options) *Report {
  1692  	index := len(prof.SampleType) - 1
  1693  	o := &options
  1694  	if o.SampleUnit == "" {
  1695  		o.SampleUnit = strings.ToLower(prof.SampleType[index].Unit)
  1696  	}
  1697  	value := func(s *profile.Sample) int64 {
  1698  		return s.Value[index]
  1699  	}
  1700  	format := func(v int64) string {
  1701  		if r := o.Ratio; r > 0 && r != 1 {
  1702  			fv := float64(v) * r
  1703  			v = int64(fv)
  1704  		}
  1705  		return scaledValueLabel(v, o.SampleUnit, o.OutputUnit)
  1706  	}
  1707  	return &Report{prof, computeTotal(prof, value), o, value, format}
  1708  }
  1709  
  1710  func computeTotal(prof *profile.Profile, value func(s *profile.Sample) int64) int64 {
  1711  	var ret int64
  1712  	for _, sample := range prof.Sample {
  1713  		ret += value(sample)
  1714  	}
  1715  	return ret
  1716  }
  1717  
  1718  // Report contains the data and associated routines to extract a
  1719  // report from a profile.
  1720  type Report struct {
  1721  	prof        *profile.Profile
  1722  	total       int64
  1723  	options     *Options
  1724  	sampleValue func(*profile.Sample) int64
  1725  	formatValue func(int64) string
  1726  }