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