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