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 IntelSyntax bool
84 }
85
86
87 func 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
118 return fmt.Errorf("unexpected output format %v", o.OutputFormat)
119 }
120
121
122
123 func (rpt *Report) newTrimmedGraph() (g *graph.Graph, origCount, droppedNodes, droppedEdges int) {
124 o := rpt.options
125
126
127
128 visualMode := o.OutputFormat == Dot
129 cumSort := o.CumSort
130
131
132 callTree := o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind)
133
134
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
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
157
158 g.SortNodes(cumSort, visualMode)
159 if nodeCount := o.NodeCount; nodeCount > 0 {
160
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
177
178 g.TrimLowFrequencyTags(nodeCutoff)
179 droppedEdges = g.TrimLowFrequencyEdges(edgeCutoff)
180 if visualMode {
181 g.RemoveRedundantEdges()
182 }
183 return
184 }
185
186 func (rpt *Report) selectOutputUnit(g *graph.Graph) {
187 o := rpt.options
188
189
190
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
221
222
223
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
235
236
237 func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph {
238 o := rpt.options
239
240
241 prof := rpt.prof
242 for _, f := range prof.Function {
243 f.Filename = trimPath(f.Filename, o.TrimPath, o.SourcePath)
244 }
245
246
247
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
269
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
286
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
296
297 func printProto(w io.Writer, rpt *Report) error {
298 p, o := rpt.prof, rpt.options
299
300
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
312 func 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
360 type functionMap map[string]*profile.Function
361
362
363
364
365
366 func (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
385 func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
386 return PrintAssembly(w, rpt, obj, -1)
387 }
388
389
390 func 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
397
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
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
437 if address == nil {
438 return fmt.Errorf("no matches found for regexp %s", o.Symbol)
439 }
440
441
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
449 for _, s := range syms {
450 sns := symNodes[s]
451
452
453 flatSum, cumSum := sns.Sum()
454
455
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
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
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
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
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
518
519 func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {
520
521
522
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
531 var objSyms []*objSymbol
532 for _, m := range prof.Mapping {
533
534
535
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
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
572
573
574 type objSymbol struct {
575 sym *plugin.Sym
576 file plugin.ObjFile
577 }
578
579
580 type orderSyms struct {
581 v []*objSymbol
582 less func(a, b *objSymbol) bool
583 }
584
585 func (o orderSyms) Len() int { return len(o.v) }
586 func (o orderSyms) Less(i, j int) bool { return o.less(o.v[i], o.v[j]) }
587 func (o orderSyms) Swap(i, j int) { o.v[i], o.v[j] = o.v[j], o.v[i] }
588
589
590 func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {
591 symNodes := make(map[*objSymbol]graph.Nodes)
592 for _, s := range symbols {
593
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
603 type 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
615 type callID struct {
616 file string
617 line int
618 }
619
620 func (a *assemblyInstruction) flatValue() int64 {
621 if a.flatDiv != 0 {
622 return a.flat / a.flatDiv
623 }
624 return a.flat
625 }
626
627 func (a *assemblyInstruction) cumValue() int64 {
628 if a.cumDiv != 0 {
629 return a.cum / a.cumDiv
630 }
631 return a.cum
632 }
633
634
635
636
637 func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, file plugin.ObjFile) []assemblyInstruction {
638
639 insts = append(insts, plugin.Inst{
640 Addr: ^uint64(0),
641 })
642
643
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
660
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
687
688 func valueOrDot(value int64, rpt *Report) string {
689 if value == 0 {
690 return "."
691 }
692 return rpt.formatValue(value)
693 }
694
695
696
697 func 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
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
762 func 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
772 type TextItem struct {
773 Name string
774 InlineLabel string
775 Flat, Cum int64
776 FlatFormat, CumFormat string
777 }
778
779
780
781 func 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
823 func 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
845 func 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
864
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
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
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
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
921 func 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
940
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
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
961 fmt.Fprintf(w, "calls=0 %s %d\n", callgrindAddress(prevInfo, callee.Info.Address), callee.Info.Lineno)
962
963
964
965
966 fmt.Fprintf(w, "* * %d\n", int64(c))
967 }
968
969 prevInfo = &n.Info
970 }
971
972 return nil
973 }
974
975
976
977
978
979
980
981
982 func 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
990
991
992
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
1011 nodeName[n] += fmt.Sprintf(" [%d/%d]", p[n.Function]+1, len(p))
1012 }
1013 }
1014 return nodeName
1015 }
1016
1017
1018
1019
1020
1021 func 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
1034
1035
1036
1037 func 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
1052 if len(relative) < len(abs) {
1053 return relative
1054 }
1055
1056 return abs
1057 }
1058
1059
1060 func 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
1079 if rx != nil && !rx.MatchString(name) {
1080 continue
1081 }
1082 matched++
1083
1084 fmt.Fprintln(w, separator)
1085
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
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
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
1127
1128 func 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
1143 func 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
1150 func 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
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
1188
1189 func 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
1229
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
1237 func 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
1249 func 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
1257
1258 func 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
1271
1272 func 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
1287
1288
1289 func 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
1318
1319 type Report struct {
1320 prof *profile.Profile
1321 total int64
1322 options *Options
1323 formatValue func(int64) string
1324 }
1325
1326
1327 func (rpt *Report) Total() int64 { return rpt.total }
1328
1329
1330 func (rpt *Report) OutputFormat() int { return rpt.options.OutputFormat }
1331
1332 func abs64(i int64) int64 {
1333 if i < 0 {
1334 return -i
1335 }
1336 return i
1337 }
1338
View as plain text