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