1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package report
18
19 import (
20 "fmt"
21 "io"
22 "net/url"
23 "path/filepath"
24 "regexp"
25 "sort"
26 "strconv"
27 "strings"
28 "text/tabwriter"
29 "time"
30
31 "github.com/google/pprof/internal/graph"
32 "github.com/google/pprof/internal/measurement"
33 "github.com/google/pprof/internal/plugin"
34 "github.com/google/pprof/profile"
35 )
36
37
38 const (
39 Callgrind = iota
40 Comments
41 Dis
42 Dot
43 List
44 Proto
45 Raw
46 Tags
47 Text
48 TopProto
49 Traces
50 Tree
51 WebList
52 )
53
54
55
56 type Options struct {
57 OutputFormat int
58
59 CumSort bool
60 CallTree bool
61 DropNegative bool
62 CompactLabels bool
63 Ratio float64
64 Title string
65 ProfileLabels []string
66 ActiveFilters []string
67 NumLabelUnits map[string]string
68
69 NodeCount int
70 NodeFraction float64
71 EdgeFraction float64
72
73 SampleValue func(s []int64) int64
74 SampleMeanDivisor func(s []int64) int64
75 SampleType string
76 SampleUnit string
77
78 OutputUnit string
79
80 Symbol *regexp.Regexp
81 SourcePath string
82 TrimPath string
83
84 IntelSyntax bool
85 }
86
87
88 func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
89 o := rpt.options
90
91 switch o.OutputFormat {
92 case Comments:
93 return printComments(w, rpt)
94 case Dot:
95 return printDOT(w, rpt)
96 case Tree:
97 return printTree(w, rpt)
98 case Text:
99 return printText(w, rpt)
100 case Traces:
101 return printTraces(w, rpt)
102 case Raw:
103 fmt.Fprint(w, rpt.prof.String())
104 return nil
105 case Tags:
106 return printTags(w, rpt)
107 case Proto:
108 return printProto(w, rpt)
109 case TopProto:
110 return printTopProto(w, rpt)
111 case Dis:
112 return printAssembly(w, rpt, obj)
113 case List:
114 return printSource(w, rpt)
115 case Callgrind:
116 return printCallgrind(w, rpt)
117 }
118
119 return fmt.Errorf("unexpected output format %v", o.OutputFormat)
120 }
121
122
123
124 func (rpt *Report) newTrimmedGraph() (g *graph.Graph, origCount, droppedNodes, droppedEdges int) {
125 o := rpt.options
126
127
128
129 visualMode := o.OutputFormat == Dot
130 cumSort := o.CumSort
131
132
133 callTree := o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind)
134
135
136 g = rpt.newGraph(nil)
137 totalValue, _ := g.Nodes.Sum()
138 nodeCutoff := abs64(int64(float64(totalValue) * o.NodeFraction))
139 edgeCutoff := abs64(int64(float64(totalValue) * o.EdgeFraction))
140
141
142 if nodeCutoff > 0 {
143 if callTree {
144 if nodesKept := g.DiscardLowFrequencyNodePtrs(nodeCutoff); len(g.Nodes) != len(nodesKept) {
145 droppedNodes = len(g.Nodes) - len(nodesKept)
146 g.TrimTree(nodesKept)
147 }
148 } else {
149 if nodesKept := g.DiscardLowFrequencyNodes(nodeCutoff); len(g.Nodes) != len(nodesKept) {
150 droppedNodes = len(g.Nodes) - len(nodesKept)
151 g = rpt.newGraph(nodesKept)
152 }
153 }
154 }
155 origCount = len(g.Nodes)
156
157
158
159 g.SortNodes(cumSort, visualMode)
160 if nodeCount := o.NodeCount; nodeCount > 0 {
161
162 g.TrimLowFrequencyTags(nodeCutoff)
163 g.TrimLowFrequencyEdges(edgeCutoff)
164 if callTree {
165 if nodesKept := g.SelectTopNodePtrs(nodeCount, visualMode); len(g.Nodes) != len(nodesKept) {
166 g.TrimTree(nodesKept)
167 g.SortNodes(cumSort, visualMode)
168 }
169 } else {
170 if nodesKept := g.SelectTopNodes(nodeCount, visualMode); len(g.Nodes) != len(nodesKept) {
171 g = rpt.newGraph(nodesKept)
172 g.SortNodes(cumSort, visualMode)
173 }
174 }
175 }
176
177
178
179 g.TrimLowFrequencyTags(nodeCutoff)
180 droppedEdges = g.TrimLowFrequencyEdges(edgeCutoff)
181 if visualMode {
182 g.RemoveRedundantEdges()
183 }
184 return
185 }
186
187 func (rpt *Report) selectOutputUnit(g *graph.Graph) {
188 o := rpt.options
189
190
191
192 if o.OutputUnit != "minimum" || len(g.Nodes) == 0 {
193 return
194 }
195 var minValue int64
196
197 for _, n := range g.Nodes {
198 nodeMin := abs64(n.FlatValue())
199 if nodeMin == 0 {
200 nodeMin = abs64(n.CumValue())
201 }
202 if nodeMin > 0 && (minValue == 0 || nodeMin < minValue) {
203 minValue = nodeMin
204 }
205 }
206 maxValue := rpt.total
207 if minValue == 0 {
208 minValue = maxValue
209 }
210
211 if r := o.Ratio; r > 0 && r != 1 {
212 minValue = int64(float64(minValue) * r)
213 maxValue = int64(float64(maxValue) * r)
214 }
215
216 _, minUnit := measurement.Scale(minValue, o.SampleUnit, "minimum")
217 _, maxUnit := measurement.Scale(maxValue, o.SampleUnit, "minimum")
218
219 unit := minUnit
220 if minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind {
221
222
223
224
225 _, unit = measurement.Scale(100*minValue, o.SampleUnit, "minimum")
226 }
227
228 if unit != "" {
229 o.OutputUnit = unit
230 } else {
231 o.OutputUnit = o.SampleUnit
232 }
233 }
234
235
236
237
238 func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph {
239 o := rpt.options
240
241
242 prof := rpt.prof
243 for _, f := range prof.Function {
244 f.Filename = trimPath(f.Filename, o.TrimPath, o.SourcePath)
245 }
246
247
248
249 for _, s := range prof.Sample {
250 numLabels := make(map[string][]int64, len(s.NumLabel))
251 numUnits := make(map[string][]string, len(s.NumLabel))
252 for k, vs := range s.NumLabel {
253 if k == "bytes" {
254 unit := o.NumLabelUnits[k]
255 numValues := make([]int64, len(vs))
256 numUnit := make([]string, len(vs))
257 for i, v := range vs {
258 numValues[i] = v
259 numUnit[i] = unit
260 }
261 numLabels[k] = append(numLabels[k], numValues...)
262 numUnits[k] = append(numUnits[k], numUnit...)
263 }
264 }
265 s.NumLabel = numLabels
266 s.NumUnit = numUnits
267 }
268
269
270
271 prof.RemoveLabel("pprof::base")
272
273 formatTag := func(v int64, key string) string {
274 return measurement.ScaledLabel(v, key, o.OutputUnit)
275 }
276
277 gopt := &graph.Options{
278 SampleValue: o.SampleValue,
279 SampleMeanDivisor: o.SampleMeanDivisor,
280 FormatTag: formatTag,
281 CallTree: o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind),
282 DropNegative: o.DropNegative,
283 KeptNodes: nodes,
284 }
285
286
287
288 switch o.OutputFormat {
289 case Raw, List, WebList, Dis, Callgrind:
290 gopt.ObjNames = true
291 }
292
293 return graph.New(rpt.prof, gopt)
294 }
295
296
297
298 func printProto(w io.Writer, rpt *Report) error {
299 p, o := rpt.prof, rpt.options
300
301
302 if r := o.Ratio; r > 0 && r != 1 {
303 for _, sample := range p.Sample {
304 for i, v := range sample.Value {
305 sample.Value[i] = int64(float64(v) * r)
306 }
307 }
308 }
309 return p.Write(w)
310 }
311
312
313 func printTopProto(w io.Writer, rpt *Report) error {
314 p := rpt.prof
315 o := rpt.options
316 g, _, _, _ := rpt.newTrimmedGraph()
317 rpt.selectOutputUnit(g)
318
319 out := profile.Profile{
320 SampleType: []*profile.ValueType{
321 {Type: "cum", Unit: o.OutputUnit},
322 {Type: "flat", Unit: o.OutputUnit},
323 },
324 TimeNanos: p.TimeNanos,
325 DurationNanos: p.DurationNanos,
326 PeriodType: p.PeriodType,
327 Period: p.Period,
328 }
329 functionMap := make(functionMap)
330 for i, n := range g.Nodes {
331 f, added := functionMap.findOrAdd(n.Info)
332 if added {
333 out.Function = append(out.Function, f)
334 }
335 flat, cum := n.FlatValue(), n.CumValue()
336 l := &profile.Location{
337 ID: uint64(i + 1),
338 Address: n.Info.Address,
339 Line: []profile.Line{
340 {
341 Line: int64(n.Info.Lineno),
342 Column: int64(n.Info.Columnno),
343 Function: f,
344 },
345 },
346 }
347
348 fv, _ := measurement.Scale(flat, o.SampleUnit, o.OutputUnit)
349 cv, _ := measurement.Scale(cum, o.SampleUnit, o.OutputUnit)
350 s := &profile.Sample{
351 Location: []*profile.Location{l},
352 Value: []int64{int64(cv), int64(fv)},
353 }
354 out.Location = append(out.Location, l)
355 out.Sample = append(out.Sample, s)
356 }
357
358 return out.Write(w)
359 }
360
361 type functionMap map[string]*profile.Function
362
363
364
365
366
367 func (fm functionMap) findOrAdd(ni graph.NodeInfo) (*profile.Function, bool) {
368 fName := fmt.Sprintf("%q%q%q%d", ni.Name, ni.OrigName, ni.File, ni.StartLine)
369
370 if f := fm[fName]; f != nil {
371 return f, false
372 }
373
374 f := &profile.Function{
375 ID: uint64(len(fm) + 1),
376 Name: ni.Name,
377 SystemName: ni.OrigName,
378 Filename: ni.File,
379 StartLine: int64(ni.StartLine),
380 }
381 fm[fName] = f
382 return f, true
383 }
384
385
386 func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
387 return PrintAssembly(w, rpt, obj, -1)
388 }
389
390
391 func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) error {
392 o := rpt.options
393 prof := rpt.prof
394
395 g := rpt.newGraph(nil)
396
397
398
399 var address *uint64
400 if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
401 address = &hex
402 }
403
404 fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total))
405 symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)
406 symNodes := nodesPerSymbol(g.Nodes, symbols)
407
408
409 var syms []*objSymbol
410 for s := range symNodes {
411 syms = append(syms, s)
412 }
413 byName := func(a, b *objSymbol) bool {
414 if na, nb := a.sym.Name[0], b.sym.Name[0]; na != nb {
415 return na < nb
416 }
417 return a.sym.Start < b.sym.Start
418 }
419 if maxFuncs < 0 {
420 sort.Sort(orderSyms{syms, byName})
421 } else {
422 byFlatSum := func(a, b *objSymbol) bool {
423 suma, _ := symNodes[a].Sum()
424 sumb, _ := symNodes[b].Sum()
425 if suma != sumb {
426 return suma > sumb
427 }
428 return byName(a, b)
429 }
430 sort.Sort(orderSyms{syms, byFlatSum})
431 if len(syms) > maxFuncs {
432 syms = syms[:maxFuncs]
433 }
434 }
435
436 if len(syms) == 0 {
437
438 if address == nil {
439 return fmt.Errorf("no matches found for regexp %s", o.Symbol)
440 }
441
442
443 if len(symbols) == 0 {
444 return fmt.Errorf("no matches found for address 0x%x", *address)
445 }
446 return fmt.Errorf("address 0x%x found in binary, but the corresponding symbols do not have samples in the profile", *address)
447 }
448
449
450 for _, s := range syms {
451 sns := symNodes[s]
452
453
454 flatSum, cumSum := sns.Sum()
455
456
457 insts, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End, o.IntelSyntax)
458 if err != nil {
459 return err
460 }
461
462 ns := annotateAssembly(insts, sns, s.file)
463
464 fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
465 for _, name := range s.sym.Name[1:] {
466 fmt.Fprintf(w, " AKA ======================== %s\n", name)
467 }
468 fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
469 rpt.formatValue(flatSum), rpt.formatValue(cumSum),
470 measurement.Percentage(cumSum, rpt.total))
471
472 function, file, line := "", "", 0
473 for _, n := range ns {
474 locStr := ""
475
476 if n.function != function || n.file != file || n.line != line {
477 function, file, line = n.function, n.file, n.line
478 if n.function != "" {
479 locStr = n.function + " "
480 }
481 if n.file != "" {
482 locStr += n.file
483 if n.line != 0 {
484 locStr += fmt.Sprintf(":%d", n.line)
485 }
486 }
487 }
488 switch {
489 case locStr == "":
490
491 fmt.Fprintf(w, "%10s %10s %10x: %s\n",
492 valueOrDot(n.flatValue(), rpt),
493 valueOrDot(n.cumValue(), rpt),
494 n.address, n.instruction,
495 )
496 case len(n.instruction) < 40:
497
498 fmt.Fprintf(w, "%10s %10s %10x: %-40s;%s\n",
499 valueOrDot(n.flatValue(), rpt),
500 valueOrDot(n.cumValue(), rpt),
501 n.address, n.instruction,
502 locStr,
503 )
504 default:
505
506 fmt.Fprintf(w, "%74s;%s\n", "", locStr)
507 fmt.Fprintf(w, "%10s %10s %10x: %s\n",
508 valueOrDot(n.flatValue(), rpt),
509 valueOrDot(n.cumValue(), rpt),
510 n.address, n.instruction,
511 )
512 }
513 }
514 }
515 return nil
516 }
517
518
519
520 func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {
521
522
523
524 fileHasSamplesAndMatched := make(map[string]bool)
525 for _, n := range g.Nodes {
526 if name := n.Info.PrintableName(); rx.MatchString(name) && n.Info.Objfile != "" {
527 fileHasSamplesAndMatched[n.Info.Objfile] = true
528 }
529 }
530
531
532 var objSyms []*objSymbol
533 for _, m := range prof.Mapping {
534
535
536
537 if !fileHasSamplesAndMatched[m.File] {
538 if address == nil || !(m.Start <= *address && *address <= m.Limit) {
539 continue
540 }
541 }
542
543 f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)
544 if err != nil {
545 fmt.Printf("%v\n", err)
546 continue
547 }
548
549
550 var addr uint64
551 if address != nil {
552 addr = *address
553 }
554 msyms, err := f.Symbols(rx, addr)
555 f.Close()
556 if err != nil {
557 continue
558 }
559 for _, ms := range msyms {
560 objSyms = append(objSyms,
561 &objSymbol{
562 sym: ms,
563 file: f,
564 },
565 )
566 }
567 }
568
569 return objSyms
570 }
571
572
573
574
575 type objSymbol struct {
576 sym *plugin.Sym
577 file plugin.ObjFile
578 }
579
580
581 type orderSyms struct {
582 v []*objSymbol
583 less func(a, b *objSymbol) bool
584 }
585
586 func (o orderSyms) Len() int { return len(o.v) }
587 func (o orderSyms) Less(i, j int) bool { return o.less(o.v[i], o.v[j]) }
588 func (o orderSyms) Swap(i, j int) { o.v[i], o.v[j] = o.v[j], o.v[i] }
589
590
591 func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {
592 symNodes := make(map[*objSymbol]graph.Nodes)
593 for _, s := range symbols {
594
595 for _, n := range ns {
596 if address, err := s.file.ObjAddr(n.Info.Address); err == nil && address >= s.sym.Start && address < s.sym.End {
597 symNodes[s] = append(symNodes[s], n)
598 }
599 }
600 }
601 return symNodes
602 }
603
604 type assemblyInstruction struct {
605 address uint64
606 instruction string
607 function string
608 file string
609 line int
610 flat, cum int64
611 flatDiv, cumDiv int64
612 startsBlock bool
613 inlineCalls []callID
614 }
615
616 type callID struct {
617 file string
618 line int
619 }
620
621 func (a *assemblyInstruction) flatValue() int64 {
622 if a.flatDiv != 0 {
623 return a.flat / a.flatDiv
624 }
625 return a.flat
626 }
627
628 func (a *assemblyInstruction) cumValue() int64 {
629 if a.cumDiv != 0 {
630 return a.cum / a.cumDiv
631 }
632 return a.cum
633 }
634
635
636
637
638 func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, file plugin.ObjFile) []assemblyInstruction {
639
640 insts = append(insts, plugin.Inst{
641 Addr: ^uint64(0),
642 })
643
644
645 samples.Sort(graph.AddressOrder)
646
647 s := 0
648 asm := make([]assemblyInstruction, 0, len(insts))
649 for ix, in := range insts[:len(insts)-1] {
650 n := assemblyInstruction{
651 address: in.Addr,
652 instruction: in.Text,
653 function: in.Function,
654 line: in.Line,
655 }
656 if in.File != "" {
657 n.file = filepath.Base(in.File)
658 }
659
660
661
662 for next := insts[ix+1].Addr; s < len(samples); s++ {
663 if addr, err := file.ObjAddr(samples[s].Info.Address); err != nil || addr >= next {
664 break
665 }
666 sample := samples[s]
667 n.flatDiv += sample.FlatDiv
668 n.flat += sample.Flat
669 n.cumDiv += sample.CumDiv
670 n.cum += sample.Cum
671 if f := sample.Info.File; f != "" && n.file == "" {
672 n.file = filepath.Base(f)
673 }
674 if ln := sample.Info.Lineno; ln != 0 && n.line == 0 {
675 n.line = ln
676 }
677 if f := sample.Info.Name; f != "" && n.function == "" {
678 n.function = f
679 }
680 }
681 asm = append(asm, n)
682 }
683
684 return asm
685 }
686
687
688
689 func valueOrDot(value int64, rpt *Report) string {
690 if value == 0 {
691 return "."
692 }
693 return rpt.formatValue(value)
694 }
695
696
697
698 func printTags(w io.Writer, rpt *Report) error {
699 p := rpt.prof
700
701 o := rpt.options
702 formatTag := func(v int64, key string) string {
703 return measurement.ScaledLabel(v, key, o.OutputUnit)
704 }
705
706
707 tagMap := make(map[string]map[string]int64)
708 for _, s := range p.Sample {
709 for key, vals := range s.Label {
710 for _, val := range vals {
711 valueMap, ok := tagMap[key]
712 if !ok {
713 valueMap = make(map[string]int64)
714 tagMap[key] = valueMap
715 }
716 valueMap[val] += o.SampleValue(s.Value)
717 }
718 }
719 for key, vals := range s.NumLabel {
720 unit := o.NumLabelUnits[key]
721 for _, nval := range vals {
722 val := formatTag(nval, unit)
723 valueMap, ok := tagMap[key]
724 if !ok {
725 valueMap = make(map[string]int64)
726 tagMap[key] = valueMap
727 }
728 valueMap[val] += o.SampleValue(s.Value)
729 }
730 }
731 }
732
733 tagKeys := make([]*graph.Tag, 0, len(tagMap))
734 for key := range tagMap {
735 tagKeys = append(tagKeys, &graph.Tag{Name: key})
736 }
737 tabw := tabwriter.NewWriter(w, 0, 0, 1, ' ', tabwriter.AlignRight)
738 for _, tagKey := range graph.SortTags(tagKeys, true) {
739 var total int64
740 key := tagKey.Name
741 tags := make([]*graph.Tag, 0, len(tagMap[key]))
742 for t, c := range tagMap[key] {
743 total += c
744 tags = append(tags, &graph.Tag{Name: t, Flat: c})
745 }
746
747 f, u := measurement.Scale(total, o.SampleUnit, o.OutputUnit)
748 fmt.Fprintf(tabw, "%s:\t Total %.1f%s\n", key, f, u)
749 for _, t := range graph.SortTags(tags, true) {
750 f, u := measurement.Scale(t.FlatValue(), o.SampleUnit, o.OutputUnit)
751 if total > 0 {
752 fmt.Fprintf(tabw, " \t%.1f%s (%s):\t %s\n", f, u, measurement.Percentage(t.FlatValue(), total), t.Name)
753 } else {
754 fmt.Fprintf(tabw, " \t%.1f%s:\t %s\n", f, u, t.Name)
755 }
756 }
757 fmt.Fprintln(tabw)
758 }
759 return tabw.Flush()
760 }
761
762
763 func printComments(w io.Writer, rpt *Report) error {
764 p := rpt.prof
765
766 for _, c := range p.Comments {
767 fmt.Fprintln(w, c)
768 }
769 return nil
770 }
771
772
773 type TextItem struct {
774 Name string
775 InlineLabel string
776 Flat, Cum int64
777 FlatFormat, CumFormat string
778 }
779
780
781
782 func TextItems(rpt *Report) ([]TextItem, []string) {
783 g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
784 rpt.selectOutputUnit(g)
785 labels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false)
786
787 var items []TextItem
788 var flatSum int64
789 for _, n := range g.Nodes {
790 name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
791
792 var inline, noinline bool
793 for _, e := range n.In {
794 if e.Inline {
795 inline = true
796 } else {
797 noinline = true
798 }
799 }
800
801 var inl string
802 if inline {
803 if noinline {
804 inl = "(partial-inline)"
805 } else {
806 inl = "(inline)"
807 }
808 }
809
810 flatSum += flat
811 items = append(items, TextItem{
812 Name: name,
813 InlineLabel: inl,
814 Flat: flat,
815 Cum: cum,
816 FlatFormat: rpt.formatValue(flat),
817 CumFormat: rpt.formatValue(cum),
818 })
819 }
820 return items, labels
821 }
822
823
824 func printText(w io.Writer, rpt *Report) error {
825 items, labels := TextItems(rpt)
826 fmt.Fprintln(w, strings.Join(labels, "\n"))
827 fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n",
828 "flat", "flat", "sum", "cum", "cum")
829 var flatSum int64
830 for _, item := range items {
831 inl := item.InlineLabel
832 if inl != "" {
833 inl = " " + inl
834 }
835 flatSum += item.Flat
836 fmt.Fprintf(w, "%10s %s %s %10s %s %s%s\n",
837 item.FlatFormat, measurement.Percentage(item.Flat, rpt.total),
838 measurement.Percentage(flatSum, rpt.total),
839 item.CumFormat, measurement.Percentage(item.Cum, rpt.total),
840 item.Name, inl)
841 }
842 return nil
843 }
844
845
846 func printTraces(w io.Writer, rpt *Report) error {
847 fmt.Fprintln(w, strings.Join(ProfileLabels(rpt), "\n"))
848
849 prof := rpt.prof
850 o := rpt.options
851
852 const separator = "-----------+-------------------------------------------------------"
853
854 _, locations := graph.CreateNodes(prof, &graph.Options{})
855 for _, sample := range prof.Sample {
856 type stk struct {
857 *graph.NodeInfo
858 inline bool
859 }
860 var stack []stk
861 for _, loc := range sample.Location {
862 nodes := locations[loc.ID]
863 for i, n := range nodes {
864
865
866 inline := i != len(nodes)-1
867 stack = append(stack, stk{&n.Info, inline})
868 }
869 }
870
871 if len(stack) == 0 {
872 continue
873 }
874
875 fmt.Fprintln(w, separator)
876
877 var labels []string
878 for s, vs := range sample.Label {
879 labels = append(labels, fmt.Sprintf("%10s: %s\n", s, strings.Join(vs, " ")))
880 }
881 sort.Strings(labels)
882 fmt.Fprint(w, strings.Join(labels, ""))
883
884
885 var numLabels []string
886 for key, vals := range sample.NumLabel {
887 unit := o.NumLabelUnits[key]
888 numValues := make([]string, len(vals))
889 for i, vv := range vals {
890 numValues[i] = measurement.Label(vv, unit)
891 }
892 numLabels = append(numLabels, fmt.Sprintf("%10s: %s\n", key, strings.Join(numValues, " ")))
893 }
894 sort.Strings(numLabels)
895 fmt.Fprint(w, strings.Join(numLabels, ""))
896
897 var d, v int64
898 v = o.SampleValue(sample.Value)
899 if o.SampleMeanDivisor != nil {
900 d = o.SampleMeanDivisor(sample.Value)
901 }
902
903 if d != 0 {
904 v = v / d
905 }
906 for i, s := range stack {
907 var vs, inline string
908 if i == 0 {
909 vs = rpt.formatValue(v)
910 }
911 if s.inline {
912 inline = " (inline)"
913 }
914 fmt.Fprintf(w, "%10s %s%s\n", vs, s.PrintableName(), inline)
915 }
916 }
917 fmt.Fprintln(w, separator)
918 return nil
919 }
920
921
922 func printCallgrind(w io.Writer, rpt *Report) error {
923 o := rpt.options
924 rpt.options.NodeFraction = 0
925 rpt.options.EdgeFraction = 0
926 rpt.options.NodeCount = 0
927
928 g, _, _, _ := rpt.newTrimmedGraph()
929 rpt.selectOutputUnit(g)
930
931 nodeNames := getDisambiguatedNames(g)
932
933 fmt.Fprintln(w, "positions: instr line")
934 fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")")
935
936 objfiles := make(map[string]int)
937 files := make(map[string]int)
938 names := make(map[string]int)
939
940
941
942 var prevInfo *graph.NodeInfo
943 for _, n := range g.Nodes {
944 if prevInfo == nil || n.Info.Objfile != prevInfo.Objfile || n.Info.File != prevInfo.File || n.Info.Name != prevInfo.Name {
945 fmt.Fprintln(w)
946 fmt.Fprintln(w, "ob="+callgrindName(objfiles, n.Info.Objfile))
947 fmt.Fprintln(w, "fl="+callgrindName(files, n.Info.File))
948 fmt.Fprintln(w, "fn="+callgrindName(names, n.Info.Name))
949 }
950
951 addr := callgrindAddress(prevInfo, n.Info.Address)
952 sv, _ := measurement.Scale(n.FlatValue(), o.SampleUnit, o.OutputUnit)
953 fmt.Fprintf(w, "%s %d %d\n", addr, n.Info.Lineno, int64(sv))
954
955
956 for _, out := range n.Out.Sort() {
957 c, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit)
958 callee := out.Dest
959 fmt.Fprintln(w, "cfl="+callgrindName(files, callee.Info.File))
960 fmt.Fprintln(w, "cfn="+callgrindName(names, nodeNames[callee]))
961
962 fmt.Fprintf(w, "calls=0 %s %d\n", callgrindAddress(prevInfo, callee.Info.Address), callee.Info.Lineno)
963
964
965
966
967 fmt.Fprintf(w, "* * %d\n", int64(c))
968 }
969
970 prevInfo = &n.Info
971 }
972
973 return nil
974 }
975
976
977
978
979
980
981
982
983 func getDisambiguatedNames(g *graph.Graph) map[*graph.Node]string {
984 nodeName := make(map[*graph.Node]string, len(g.Nodes))
985
986 type names struct {
987 file, function string
988 }
989
990
991
992
993
994 nameFunctionIndex := make(map[names]map[*graph.Node]int)
995 for _, n := range g.Nodes {
996 nm := names{n.Info.File, n.Info.Name}
997 p, ok := nameFunctionIndex[nm]
998 if !ok {
999 p = make(map[*graph.Node]int)
1000 nameFunctionIndex[nm] = p
1001 }
1002 if _, ok := p[n.Function]; !ok {
1003 p[n.Function] = len(p)
1004 }
1005 }
1006
1007 for _, n := range g.Nodes {
1008 nm := names{n.Info.File, n.Info.Name}
1009 nodeName[n] = n.Info.Name
1010 if p := nameFunctionIndex[nm]; len(p) > 1 {
1011
1012 nodeName[n] += fmt.Sprintf(" [%d/%d]", p[n.Function]+1, len(p))
1013 }
1014 }
1015 return nodeName
1016 }
1017
1018
1019
1020
1021
1022 func callgrindName(names map[string]int, name string) string {
1023 if name == "" {
1024 return ""
1025 }
1026 if id, ok := names[name]; ok {
1027 return fmt.Sprintf("(%d)", id)
1028 }
1029 id := len(names) + 1
1030 names[name] = id
1031 return fmt.Sprintf("(%d) %s", id, name)
1032 }
1033
1034
1035
1036
1037
1038 func callgrindAddress(prevInfo *graph.NodeInfo, curr uint64) string {
1039 abs := fmt.Sprintf("%#x", curr)
1040 if prevInfo == nil {
1041 return abs
1042 }
1043
1044 prev := prevInfo.Address
1045 if prev == curr {
1046 return "*"
1047 }
1048
1049 diff := int64(curr - prev)
1050 relative := fmt.Sprintf("%+d", diff)
1051
1052
1053 if len(relative) < len(abs) {
1054 return relative
1055 }
1056
1057 return abs
1058 }
1059
1060
1061 func printTree(w io.Writer, rpt *Report) error {
1062 const separator = "----------------------------------------------------------+-------------"
1063 const legend = " flat flat% sum% cum cum% calls calls% + context "
1064
1065 g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
1066 rpt.selectOutputUnit(g)
1067
1068 fmt.Fprintln(w, strings.Join(reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false), "\n"))
1069
1070 fmt.Fprintln(w, separator)
1071 fmt.Fprintln(w, legend)
1072 var flatSum int64
1073
1074 rx := rpt.options.Symbol
1075 matched := 0
1076 for _, n := range g.Nodes {
1077 name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
1078
1079
1080 if rx != nil && !rx.MatchString(name) {
1081 continue
1082 }
1083 matched++
1084
1085 fmt.Fprintln(w, separator)
1086
1087 inEdges := n.In.Sort()
1088 for _, in := range inEdges {
1089 var inline string
1090 if in.Inline {
1091 inline = " (inline)"
1092 }
1093 fmt.Fprintf(w, "%50s %s | %s%s\n", rpt.formatValue(in.Weight),
1094 measurement.Percentage(in.Weight, cum), in.Src.Info.PrintableName(), inline)
1095 }
1096
1097
1098 flatSum += flat
1099 fmt.Fprintf(w, "%10s %s %s %10s %s | %s\n",
1100 rpt.formatValue(flat),
1101 measurement.Percentage(flat, rpt.total),
1102 measurement.Percentage(flatSum, rpt.total),
1103 rpt.formatValue(cum),
1104 measurement.Percentage(cum, rpt.total),
1105 name)
1106
1107
1108 outEdges := n.Out.Sort()
1109 for _, out := range outEdges {
1110 var inline string
1111 if out.Inline {
1112 inline = " (inline)"
1113 }
1114 fmt.Fprintf(w, "%50s %s | %s%s\n", rpt.formatValue(out.Weight),
1115 measurement.Percentage(out.Weight, cum), out.Dest.Info.PrintableName(), inline)
1116 }
1117 }
1118 if len(g.Nodes) > 0 {
1119 fmt.Fprintln(w, separator)
1120 }
1121 if rx != nil && matched == 0 {
1122 return fmt.Errorf("no matches found for regexp: %s", rx)
1123 }
1124 return nil
1125 }
1126
1127
1128
1129 func GetDOT(rpt *Report) (*graph.Graph, *graph.DotConfig) {
1130 g, origCount, droppedNodes, droppedEdges := rpt.newTrimmedGraph()
1131 rpt.selectOutputUnit(g)
1132 labels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, droppedEdges, true)
1133
1134 c := &graph.DotConfig{
1135 Title: rpt.options.Title,
1136 Labels: labels,
1137 FormatValue: rpt.formatValue,
1138 Total: rpt.total,
1139 }
1140 return g, c
1141 }
1142
1143
1144 func printDOT(w io.Writer, rpt *Report) error {
1145 g, c := GetDOT(rpt)
1146 graph.ComposeDot(w, g, &graph.DotAttributes{}, c)
1147 return nil
1148 }
1149
1150
1151 func ProfileLabels(rpt *Report) []string {
1152 label := []string{}
1153 prof := rpt.prof
1154 o := rpt.options
1155 if len(prof.Mapping) > 0 {
1156 if prof.Mapping[0].File != "" {
1157 label = append(label, "File: "+filepath.Base(prof.Mapping[0].File))
1158 }
1159 if prof.Mapping[0].BuildID != "" {
1160 label = append(label, "Build ID: "+prof.Mapping[0].BuildID)
1161 }
1162 }
1163
1164 for _, c := range prof.Comments {
1165 if !strings.HasPrefix(c, "#") {
1166 label = append(label, c)
1167 }
1168 }
1169 if o.SampleType != "" {
1170 label = append(label, "Type: "+o.SampleType)
1171 }
1172 if url := prof.DocURL; url != "" {
1173 label = append(label, "Doc: "+url)
1174 }
1175 if prof.TimeNanos != 0 {
1176 const layout = "2006-01-02 15:04:05 MST"
1177 label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout))
1178 }
1179 if prof.DurationNanos != 0 {
1180 duration := measurement.Label(prof.DurationNanos, "nanoseconds")
1181 totalNanos, totalUnit := measurement.Scale(rpt.total, o.SampleUnit, "nanoseconds")
1182 var ratio string
1183 if totalUnit == "ns" && totalNanos != 0 {
1184 ratio = "(" + measurement.Percentage(int64(totalNanos), prof.DurationNanos) + ")"
1185 }
1186 label = append(label, fmt.Sprintf("Duration: %s, Total samples = %s %s", duration, rpt.formatValue(rpt.total), ratio))
1187 }
1188 return label
1189 }
1190
1191 func graphTotal(g *graph.Graph) int64 {
1192 var total int64
1193 for _, n := range g.Nodes {
1194 total += n.FlatValue()
1195 }
1196 return total
1197 }
1198
1199
1200
1201 func reportLabels(rpt *Report, shownTotal int64, nodeCount, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {
1202 nodeFraction := rpt.options.NodeFraction
1203 edgeFraction := rpt.options.EdgeFraction
1204
1205 var label []string
1206 if len(rpt.options.ProfileLabels) > 0 {
1207 label = append(label, rpt.options.ProfileLabels...)
1208 } else if fullHeaders || !rpt.options.CompactLabels {
1209 label = ProfileLabels(rpt)
1210 }
1211
1212 if len(rpt.options.ActiveFilters) > 0 {
1213 activeFilters := legendActiveFilters(rpt.options.ActiveFilters)
1214 label = append(label, activeFilters...)
1215 }
1216
1217 label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(shownTotal), strings.TrimSpace(measurement.Percentage(shownTotal, rpt.total)), rpt.formatValue(rpt.total)))
1218
1219 if rpt.total != 0 {
1220 if droppedNodes > 0 {
1221 label = append(label, genLabel(droppedNodes, "node", "cum",
1222 rpt.formatValue(abs64(int64(float64(rpt.total)*nodeFraction)))))
1223 }
1224 if droppedEdges > 0 {
1225 label = append(label, genLabel(droppedEdges, "edge", "freq",
1226 rpt.formatValue(abs64(int64(float64(rpt.total)*edgeFraction)))))
1227 }
1228 if nodeCount > 0 && nodeCount < origCount {
1229 label = append(label, fmt.Sprintf("Showing top %d nodes out of %d",
1230 nodeCount, origCount))
1231 }
1232 }
1233
1234
1235
1236 if fullHeaders {
1237 label = append(label, "\nSee https://git.io/JfYMW for how to read the graph")
1238 }
1239
1240 return label
1241 }
1242
1243 func legendActiveFilters(activeFilters []string) []string {
1244 legendActiveFilters := make([]string, len(activeFilters)+1)
1245 legendActiveFilters[0] = "Active filters:"
1246 for i, s := range activeFilters {
1247 if len(s) > 80 {
1248 s = s[:80] + "…"
1249 }
1250 legendActiveFilters[i+1] = " " + s
1251 }
1252 return legendActiveFilters
1253 }
1254
1255 func genLabel(d int, n, l, f string) string {
1256 if d > 1 {
1257 n = n + "s"
1258 }
1259 return fmt.Sprintf("Dropped %d %s (%s <= %s)", d, n, l, f)
1260 }
1261
1262
1263
1264 func New(prof *profile.Profile, o *Options) *Report {
1265 format := func(v int64) string {
1266 if r := o.Ratio; r > 0 && r != 1 {
1267 fv := float64(v) * r
1268 v = int64(fv)
1269 }
1270 return measurement.ScaledLabel(v, o.SampleUnit, o.OutputUnit)
1271 }
1272 return &Report{prof, computeTotal(prof, o.SampleValue, o.SampleMeanDivisor),
1273 o, format}
1274 }
1275
1276
1277
1278 func NewDefault(prof *profile.Profile, options Options) *Report {
1279 index := len(prof.SampleType) - 1
1280 o := &options
1281 if o.Title == "" && len(prof.Mapping) > 0 && prof.Mapping[0].File != "" {
1282 o.Title = filepath.Base(prof.Mapping[0].File)
1283 }
1284 o.SampleType = prof.SampleType[index].Type
1285 o.SampleUnit = strings.ToLower(prof.SampleType[index].Unit)
1286 o.SampleValue = func(v []int64) int64 {
1287 return v[index]
1288 }
1289 return New(prof, o)
1290 }
1291
1292
1293
1294
1295 func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) int64 {
1296 var div, total, diffDiv, diffTotal int64
1297 for _, sample := range prof.Sample {
1298 var d, v int64
1299 v = value(sample.Value)
1300 if meanDiv != nil {
1301 d = meanDiv(sample.Value)
1302 }
1303 if v < 0 {
1304 v = -v
1305 }
1306 total += v
1307 div += d
1308 if sample.DiffBaseSample() {
1309 diffTotal += v
1310 diffDiv += d
1311 }
1312 }
1313 if diffTotal > 0 {
1314 total = diffTotal
1315 div = diffDiv
1316 }
1317 if div != 0 {
1318 return total / div
1319 }
1320 return total
1321 }
1322
1323
1324
1325 type Report struct {
1326 prof *profile.Profile
1327 total int64
1328 options *Options
1329 formatValue func(int64) string
1330 }
1331
1332
1333 func (rpt *Report) Total() int64 { return rpt.total }
1334
1335
1336 func (rpt *Report) OutputFormat() int { return rpt.options.OutputFormat }
1337
1338
1339 func (rpt *Report) DocURL() string {
1340 u := rpt.prof.DocURL
1341 if u == "" || !absoluteURL(u) {
1342 return ""
1343 }
1344 return u
1345 }
1346
1347 func absoluteURL(str string) bool {
1348
1349
1350 u, err := url.Parse(str)
1351 return err == nil && (u.Scheme == "https" || u.Scheme == "http")
1352 }
1353
1354 func abs64(i int64) int64 {
1355 if i < 0 {
1356 return -i
1357 }
1358 return i
1359 }
1360
View as plain text