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 WebList:
115 return printWebSource(w, rpt, obj)
116 case Callgrind:
117 return printCallgrind(w, rpt)
118 }
119 return fmt.Errorf("unexpected output format")
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 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 return fmt.Errorf("no matches found for regexp: %s", o.Symbol)
437 }
438
439
440 for _, s := range syms {
441 sns := symNodes[s]
442
443
444 flatSum, cumSum := sns.Sum()
445
446
447 insts, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End, o.IntelSyntax)
448 if err != nil {
449 return err
450 }
451
452 ns := annotateAssembly(insts, sns, s.file)
453
454 fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
455 for _, name := range s.sym.Name[1:] {
456 fmt.Fprintf(w, " AKA ======================== %s\n", name)
457 }
458 fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
459 rpt.formatValue(flatSum), rpt.formatValue(cumSum),
460 measurement.Percentage(cumSum, rpt.total))
461
462 function, file, line := "", "", 0
463 for _, n := range ns {
464 locStr := ""
465
466 if n.function != function || n.file != file || n.line != line {
467 function, file, line = n.function, n.file, n.line
468 if n.function != "" {
469 locStr = n.function + " "
470 }
471 if n.file != "" {
472 locStr += n.file
473 if n.line != 0 {
474 locStr += fmt.Sprintf(":%d", n.line)
475 }
476 }
477 }
478 switch {
479 case locStr == "":
480
481 fmt.Fprintf(w, "%10s %10s %10x: %s\n",
482 valueOrDot(n.flatValue(), rpt),
483 valueOrDot(n.cumValue(), rpt),
484 n.address, n.instruction,
485 )
486 case len(n.instruction) < 40:
487
488 fmt.Fprintf(w, "%10s %10s %10x: %-40s;%s\n",
489 valueOrDot(n.flatValue(), rpt),
490 valueOrDot(n.cumValue(), rpt),
491 n.address, n.instruction,
492 locStr,
493 )
494 default:
495
496 fmt.Fprintf(w, "%74s;%s\n", "", locStr)
497 fmt.Fprintf(w, "%10s %10s %10x: %s\n",
498 valueOrDot(n.flatValue(), rpt),
499 valueOrDot(n.cumValue(), rpt),
500 n.address, n.instruction,
501 )
502 }
503 }
504 }
505 return nil
506 }
507
508
509
510 func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {
511 hasSamples := make(map[string]bool)
512
513
514 for _, n := range g.Nodes {
515 if name := n.Info.PrintableName(); rx.MatchString(name) && n.Info.Objfile != "" {
516 hasSamples[n.Info.Objfile] = true
517 }
518 }
519
520
521 var objSyms []*objSymbol
522 for _, m := range prof.Mapping {
523 if !hasSamples[m.File] {
524 if address == nil || !(m.Start <= *address && *address <= m.Limit) {
525 continue
526 }
527 }
528
529 f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
530 if err != nil {
531 fmt.Printf("%v\n", err)
532 continue
533 }
534
535
536 var addr uint64
537 if address != nil {
538 addr = *address
539 }
540 msyms, err := f.Symbols(rx, addr)
541 f.Close()
542 if err != nil {
543 continue
544 }
545 for _, ms := range msyms {
546 objSyms = append(objSyms,
547 &objSymbol{
548 sym: ms,
549 file: f,
550 },
551 )
552 }
553 }
554
555 return objSyms
556 }
557
558
559
560
561 type objSymbol struct {
562 sym *plugin.Sym
563 file plugin.ObjFile
564 }
565
566
567 type orderSyms struct {
568 v []*objSymbol
569 less func(a, b *objSymbol) bool
570 }
571
572 func (o orderSyms) Len() int { return len(o.v) }
573 func (o orderSyms) Less(i, j int) bool { return o.less(o.v[i], o.v[j]) }
574 func (o orderSyms) Swap(i, j int) { o.v[i], o.v[j] = o.v[j], o.v[i] }
575
576
577 func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {
578 symNodes := make(map[*objSymbol]graph.Nodes)
579 for _, s := range symbols {
580
581 for _, n := range ns {
582 if address, err := s.file.ObjAddr(n.Info.Address); err == nil && address >= s.sym.Start && address < s.sym.End {
583 symNodes[s] = append(symNodes[s], n)
584 }
585 }
586 }
587 return symNodes
588 }
589
590 type assemblyInstruction struct {
591 address uint64
592 instruction string
593 function string
594 file string
595 line int
596 flat, cum int64
597 flatDiv, cumDiv int64
598 startsBlock bool
599 inlineCalls []callID
600 }
601
602 type callID struct {
603 file string
604 line int
605 }
606
607 func (a *assemblyInstruction) flatValue() int64 {
608 if a.flatDiv != 0 {
609 return a.flat / a.flatDiv
610 }
611 return a.flat
612 }
613
614 func (a *assemblyInstruction) cumValue() int64 {
615 if a.cumDiv != 0 {
616 return a.cum / a.cumDiv
617 }
618 return a.cum
619 }
620
621
622
623
624 func annotateAssembly(insts []plugin.Inst, samples graph.Nodes, file plugin.ObjFile) []assemblyInstruction {
625
626 insts = append(insts, plugin.Inst{
627 Addr: ^uint64(0),
628 })
629
630
631 samples.Sort(graph.AddressOrder)
632
633 s := 0
634 asm := make([]assemblyInstruction, 0, len(insts))
635 for ix, in := range insts[:len(insts)-1] {
636 n := assemblyInstruction{
637 address: in.Addr,
638 instruction: in.Text,
639 function: in.Function,
640 line: in.Line,
641 }
642 if in.File != "" {
643 n.file = filepath.Base(in.File)
644 }
645
646
647
648 for next := insts[ix+1].Addr; s < len(samples); s++ {
649 if addr, err := file.ObjAddr(samples[s].Info.Address); err != nil || addr >= next {
650 break
651 }
652 sample := samples[s]
653 n.flatDiv += sample.FlatDiv
654 n.flat += sample.Flat
655 n.cumDiv += sample.CumDiv
656 n.cum += sample.Cum
657 if f := sample.Info.File; f != "" && n.file == "" {
658 n.file = filepath.Base(f)
659 }
660 if ln := sample.Info.Lineno; ln != 0 && n.line == 0 {
661 n.line = ln
662 }
663 if f := sample.Info.Name; f != "" && n.function == "" {
664 n.function = f
665 }
666 }
667 asm = append(asm, n)
668 }
669
670 return asm
671 }
672
673
674
675 func valueOrDot(value int64, rpt *Report) string {
676 if value == 0 {
677 return "."
678 }
679 return rpt.formatValue(value)
680 }
681
682
683
684 func printTags(w io.Writer, rpt *Report) error {
685 p := rpt.prof
686
687 o := rpt.options
688 formatTag := func(v int64, key string) string {
689 return measurement.ScaledLabel(v, key, o.OutputUnit)
690 }
691
692
693 tagMap := make(map[string]map[string]int64)
694 for _, s := range p.Sample {
695 for key, vals := range s.Label {
696 for _, val := range vals {
697 valueMap, ok := tagMap[key]
698 if !ok {
699 valueMap = make(map[string]int64)
700 tagMap[key] = valueMap
701 }
702 valueMap[val] += o.SampleValue(s.Value)
703 }
704 }
705 for key, vals := range s.NumLabel {
706 unit := o.NumLabelUnits[key]
707 for _, nval := range vals {
708 val := formatTag(nval, unit)
709 valueMap, ok := tagMap[key]
710 if !ok {
711 valueMap = make(map[string]int64)
712 tagMap[key] = valueMap
713 }
714 valueMap[val] += o.SampleValue(s.Value)
715 }
716 }
717 }
718
719 tagKeys := make([]*graph.Tag, 0, len(tagMap))
720 for key := range tagMap {
721 tagKeys = append(tagKeys, &graph.Tag{Name: key})
722 }
723 tabw := tabwriter.NewWriter(w, 0, 0, 1, ' ', tabwriter.AlignRight)
724 for _, tagKey := range graph.SortTags(tagKeys, true) {
725 var total int64
726 key := tagKey.Name
727 tags := make([]*graph.Tag, 0, len(tagMap[key]))
728 for t, c := range tagMap[key] {
729 total += c
730 tags = append(tags, &graph.Tag{Name: t, Flat: c})
731 }
732
733 f, u := measurement.Scale(total, o.SampleUnit, o.OutputUnit)
734 fmt.Fprintf(tabw, "%s:\t Total %.1f%s\n", key, f, u)
735 for _, t := range graph.SortTags(tags, true) {
736 f, u := measurement.Scale(t.FlatValue(), o.SampleUnit, o.OutputUnit)
737 if total > 0 {
738 fmt.Fprintf(tabw, " \t%.1f%s (%s):\t %s\n", f, u, measurement.Percentage(t.FlatValue(), total), t.Name)
739 } else {
740 fmt.Fprintf(tabw, " \t%.1f%s:\t %s\n", f, u, t.Name)
741 }
742 }
743 fmt.Fprintln(tabw)
744 }
745 return tabw.Flush()
746 }
747
748
749 func printComments(w io.Writer, rpt *Report) error {
750 p := rpt.prof
751
752 for _, c := range p.Comments {
753 fmt.Fprintln(w, c)
754 }
755 return nil
756 }
757
758
759 type TextItem struct {
760 Name string
761 InlineLabel string
762 Flat, Cum int64
763 FlatFormat, CumFormat string
764 }
765
766
767
768 func TextItems(rpt *Report) ([]TextItem, []string) {
769 g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
770 rpt.selectOutputUnit(g)
771 labels := reportLabels(rpt, g, origCount, droppedNodes, 0, false)
772
773 var items []TextItem
774 var flatSum int64
775 for _, n := range g.Nodes {
776 name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
777
778 var inline, noinline bool
779 for _, e := range n.In {
780 if e.Inline {
781 inline = true
782 } else {
783 noinline = true
784 }
785 }
786
787 var inl string
788 if inline {
789 if noinline {
790 inl = "(partial-inline)"
791 } else {
792 inl = "(inline)"
793 }
794 }
795
796 flatSum += flat
797 items = append(items, TextItem{
798 Name: name,
799 InlineLabel: inl,
800 Flat: flat,
801 Cum: cum,
802 FlatFormat: rpt.formatValue(flat),
803 CumFormat: rpt.formatValue(cum),
804 })
805 }
806 return items, labels
807 }
808
809
810 func printText(w io.Writer, rpt *Report) error {
811 items, labels := TextItems(rpt)
812 fmt.Fprintln(w, strings.Join(labels, "\n"))
813 fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n",
814 "flat", "flat", "sum", "cum", "cum")
815 var flatSum int64
816 for _, item := range items {
817 inl := item.InlineLabel
818 if inl != "" {
819 inl = " " + inl
820 }
821 flatSum += item.Flat
822 fmt.Fprintf(w, "%10s %s %s %10s %s %s%s\n",
823 item.FlatFormat, measurement.Percentage(item.Flat, rpt.total),
824 measurement.Percentage(flatSum, rpt.total),
825 item.CumFormat, measurement.Percentage(item.Cum, rpt.total),
826 item.Name, inl)
827 }
828 return nil
829 }
830
831
832 func printTraces(w io.Writer, rpt *Report) error {
833 fmt.Fprintln(w, strings.Join(ProfileLabels(rpt), "\n"))
834
835 prof := rpt.prof
836 o := rpt.options
837
838 const separator = "-----------+-------------------------------------------------------"
839
840 _, locations := graph.CreateNodes(prof, &graph.Options{})
841 for _, sample := range prof.Sample {
842 type stk struct {
843 *graph.NodeInfo
844 inline bool
845 }
846 var stack []stk
847 for _, loc := range sample.Location {
848 nodes := locations[loc.ID]
849 for i, n := range nodes {
850
851
852 inline := i != len(nodes)-1
853 stack = append(stack, stk{&n.Info, inline})
854 }
855 }
856
857 if len(stack) == 0 {
858 continue
859 }
860
861 fmt.Fprintln(w, separator)
862
863 var labels []string
864 for s, vs := range sample.Label {
865 labels = append(labels, fmt.Sprintf("%10s: %s\n", s, strings.Join(vs, " ")))
866 }
867 sort.Strings(labels)
868 fmt.Fprint(w, strings.Join(labels, ""))
869
870
871 var numLabels []string
872 for key, vals := range sample.NumLabel {
873 unit := o.NumLabelUnits[key]
874 numValues := make([]string, len(vals))
875 for i, vv := range vals {
876 numValues[i] = measurement.Label(vv, unit)
877 }
878 numLabels = append(numLabels, fmt.Sprintf("%10s: %s\n", key, strings.Join(numValues, " ")))
879 }
880 sort.Strings(numLabels)
881 fmt.Fprint(w, strings.Join(numLabels, ""))
882
883 var d, v int64
884 v = o.SampleValue(sample.Value)
885 if o.SampleMeanDivisor != nil {
886 d = o.SampleMeanDivisor(sample.Value)
887 }
888
889 if d != 0 {
890 v = v / d
891 }
892 for i, s := range stack {
893 var vs, inline string
894 if i == 0 {
895 vs = rpt.formatValue(v)
896 }
897 if s.inline {
898 inline = " (inline)"
899 }
900 fmt.Fprintf(w, "%10s %s%s\n", vs, s.PrintableName(), inline)
901 }
902 }
903 fmt.Fprintln(w, separator)
904 return nil
905 }
906
907
908 func printCallgrind(w io.Writer, rpt *Report) error {
909 o := rpt.options
910 rpt.options.NodeFraction = 0
911 rpt.options.EdgeFraction = 0
912 rpt.options.NodeCount = 0
913
914 g, _, _, _ := rpt.newTrimmedGraph()
915 rpt.selectOutputUnit(g)
916
917 nodeNames := getDisambiguatedNames(g)
918
919 fmt.Fprintln(w, "positions: instr line")
920 fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")")
921
922 objfiles := make(map[string]int)
923 files := make(map[string]int)
924 names := make(map[string]int)
925
926
927
928 var prevInfo *graph.NodeInfo
929 for _, n := range g.Nodes {
930 if prevInfo == nil || n.Info.Objfile != prevInfo.Objfile || n.Info.File != prevInfo.File || n.Info.Name != prevInfo.Name {
931 fmt.Fprintln(w)
932 fmt.Fprintln(w, "ob="+callgrindName(objfiles, n.Info.Objfile))
933 fmt.Fprintln(w, "fl="+callgrindName(files, n.Info.File))
934 fmt.Fprintln(w, "fn="+callgrindName(names, n.Info.Name))
935 }
936
937 addr := callgrindAddress(prevInfo, n.Info.Address)
938 sv, _ := measurement.Scale(n.FlatValue(), o.SampleUnit, o.OutputUnit)
939 fmt.Fprintf(w, "%s %d %d\n", addr, n.Info.Lineno, int64(sv))
940
941
942 for _, out := range n.Out.Sort() {
943 c, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit)
944 callee := out.Dest
945 fmt.Fprintln(w, "cfl="+callgrindName(files, callee.Info.File))
946 fmt.Fprintln(w, "cfn="+callgrindName(names, nodeNames[callee]))
947
948 fmt.Fprintf(w, "calls=0 %s %d\n", callgrindAddress(prevInfo, callee.Info.Address), callee.Info.Lineno)
949
950
951
952
953 fmt.Fprintf(w, "* * %d\n", int64(c))
954 }
955
956 prevInfo = &n.Info
957 }
958
959 return nil
960 }
961
962
963
964
965
966
967
968
969 func getDisambiguatedNames(g *graph.Graph) map[*graph.Node]string {
970 nodeName := make(map[*graph.Node]string, len(g.Nodes))
971
972 type names struct {
973 file, function string
974 }
975
976
977
978
979
980 nameFunctionIndex := make(map[names]map[*graph.Node]int)
981 for _, n := range g.Nodes {
982 nm := names{n.Info.File, n.Info.Name}
983 p, ok := nameFunctionIndex[nm]
984 if !ok {
985 p = make(map[*graph.Node]int)
986 nameFunctionIndex[nm] = p
987 }
988 if _, ok := p[n.Function]; !ok {
989 p[n.Function] = len(p)
990 }
991 }
992
993 for _, n := range g.Nodes {
994 nm := names{n.Info.File, n.Info.Name}
995 nodeName[n] = n.Info.Name
996 if p := nameFunctionIndex[nm]; len(p) > 1 {
997
998 nodeName[n] += fmt.Sprintf(" [%d/%d]", p[n.Function]+1, len(p))
999 }
1000 }
1001 return nodeName
1002 }
1003
1004
1005
1006
1007
1008 func callgrindName(names map[string]int, name string) string {
1009 if name == "" {
1010 return ""
1011 }
1012 if id, ok := names[name]; ok {
1013 return fmt.Sprintf("(%d)", id)
1014 }
1015 id := len(names) + 1
1016 names[name] = id
1017 return fmt.Sprintf("(%d) %s", id, name)
1018 }
1019
1020
1021
1022
1023
1024 func callgrindAddress(prevInfo *graph.NodeInfo, curr uint64) string {
1025 abs := fmt.Sprintf("%#x", curr)
1026 if prevInfo == nil {
1027 return abs
1028 }
1029
1030 prev := prevInfo.Address
1031 if prev == curr {
1032 return "*"
1033 }
1034
1035 diff := int64(curr - prev)
1036 relative := fmt.Sprintf("%+d", diff)
1037
1038
1039 if len(relative) < len(abs) {
1040 return relative
1041 }
1042
1043 return abs
1044 }
1045
1046
1047 func printTree(w io.Writer, rpt *Report) error {
1048 const separator = "----------------------------------------------------------+-------------"
1049 const legend = " flat flat% sum% cum cum% calls calls% + context "
1050
1051 g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
1052 rpt.selectOutputUnit(g)
1053
1054 fmt.Fprintln(w, strings.Join(reportLabels(rpt, g, origCount, droppedNodes, 0, false), "\n"))
1055
1056 fmt.Fprintln(w, separator)
1057 fmt.Fprintln(w, legend)
1058 var flatSum int64
1059
1060 rx := rpt.options.Symbol
1061 matched := 0
1062 for _, n := range g.Nodes {
1063 name, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()
1064
1065
1066 if rx != nil && !rx.MatchString(name) {
1067 continue
1068 }
1069 matched++
1070
1071 fmt.Fprintln(w, separator)
1072
1073 inEdges := n.In.Sort()
1074 for _, in := range inEdges {
1075 var inline string
1076 if in.Inline {
1077 inline = " (inline)"
1078 }
1079 fmt.Fprintf(w, "%50s %s | %s%s\n", rpt.formatValue(in.Weight),
1080 measurement.Percentage(in.Weight, cum), in.Src.Info.PrintableName(), inline)
1081 }
1082
1083
1084 flatSum += flat
1085 fmt.Fprintf(w, "%10s %s %s %10s %s | %s\n",
1086 rpt.formatValue(flat),
1087 measurement.Percentage(flat, rpt.total),
1088 measurement.Percentage(flatSum, rpt.total),
1089 rpt.formatValue(cum),
1090 measurement.Percentage(cum, rpt.total),
1091 name)
1092
1093
1094 outEdges := n.Out.Sort()
1095 for _, out := range outEdges {
1096 var inline string
1097 if out.Inline {
1098 inline = " (inline)"
1099 }
1100 fmt.Fprintf(w, "%50s %s | %s%s\n", rpt.formatValue(out.Weight),
1101 measurement.Percentage(out.Weight, cum), out.Dest.Info.PrintableName(), inline)
1102 }
1103 }
1104 if len(g.Nodes) > 0 {
1105 fmt.Fprintln(w, separator)
1106 }
1107 if rx != nil && matched == 0 {
1108 return fmt.Errorf("no matches found for regexp: %s", rx)
1109 }
1110 return nil
1111 }
1112
1113
1114
1115 func GetDOT(rpt *Report) (*graph.Graph, *graph.DotConfig) {
1116 g, origCount, droppedNodes, droppedEdges := rpt.newTrimmedGraph()
1117 rpt.selectOutputUnit(g)
1118 labels := reportLabels(rpt, g, origCount, droppedNodes, droppedEdges, true)
1119
1120 c := &graph.DotConfig{
1121 Title: rpt.options.Title,
1122 Labels: labels,
1123 FormatValue: rpt.formatValue,
1124 Total: rpt.total,
1125 }
1126 return g, c
1127 }
1128
1129
1130 func printDOT(w io.Writer, rpt *Report) error {
1131 g, c := GetDOT(rpt)
1132 graph.ComposeDot(w, g, &graph.DotAttributes{}, c)
1133 return nil
1134 }
1135
1136
1137 func ProfileLabels(rpt *Report) []string {
1138 label := []string{}
1139 prof := rpt.prof
1140 o := rpt.options
1141 if len(prof.Mapping) > 0 {
1142 if prof.Mapping[0].File != "" {
1143 label = append(label, "File: "+filepath.Base(prof.Mapping[0].File))
1144 }
1145 if prof.Mapping[0].BuildID != "" {
1146 label = append(label, "Build ID: "+prof.Mapping[0].BuildID)
1147 }
1148 }
1149
1150 for _, c := range prof.Comments {
1151 if !strings.HasPrefix(c, "#") {
1152 label = append(label, c)
1153 }
1154 }
1155 if o.SampleType != "" {
1156 label = append(label, "Type: "+o.SampleType)
1157 }
1158 if prof.TimeNanos != 0 {
1159 const layout = "Jan 2, 2006 at 3:04pm (MST)"
1160 label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout))
1161 }
1162 if prof.DurationNanos != 0 {
1163 duration := measurement.Label(prof.DurationNanos, "nanoseconds")
1164 totalNanos, totalUnit := measurement.Scale(rpt.total, o.SampleUnit, "nanoseconds")
1165 var ratio string
1166 if totalUnit == "ns" && totalNanos != 0 {
1167 ratio = "(" + measurement.Percentage(int64(totalNanos), prof.DurationNanos) + ")"
1168 }
1169 label = append(label, fmt.Sprintf("Duration: %s, Total samples = %s %s", duration, rpt.formatValue(rpt.total), ratio))
1170 }
1171 return label
1172 }
1173
1174
1175
1176 func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {
1177 nodeFraction := rpt.options.NodeFraction
1178 edgeFraction := rpt.options.EdgeFraction
1179 nodeCount := len(g.Nodes)
1180
1181 var label []string
1182 if len(rpt.options.ProfileLabels) > 0 {
1183 label = append(label, rpt.options.ProfileLabels...)
1184 } else if fullHeaders || !rpt.options.CompactLabels {
1185 label = ProfileLabels(rpt)
1186 }
1187
1188 var flatSum int64
1189 for _, n := range g.Nodes {
1190 flatSum = flatSum + n.FlatValue()
1191 }
1192
1193 if len(rpt.options.ActiveFilters) > 0 {
1194 activeFilters := legendActiveFilters(rpt.options.ActiveFilters)
1195 label = append(label, activeFilters...)
1196 }
1197
1198 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)))
1199
1200 if rpt.total != 0 {
1201 if droppedNodes > 0 {
1202 label = append(label, genLabel(droppedNodes, "node", "cum",
1203 rpt.formatValue(abs64(int64(float64(rpt.total)*nodeFraction)))))
1204 }
1205 if droppedEdges > 0 {
1206 label = append(label, genLabel(droppedEdges, "edge", "freq",
1207 rpt.formatValue(abs64(int64(float64(rpt.total)*edgeFraction)))))
1208 }
1209 if nodeCount > 0 && nodeCount < origCount {
1210 label = append(label, fmt.Sprintf("Showing top %d nodes out of %d",
1211 nodeCount, origCount))
1212 }
1213 }
1214
1215
1216
1217 if fullHeaders {
1218 label = append(label, "\nSee https://git.io/JfYMW for how to read the graph")
1219 }
1220
1221 return label
1222 }
1223
1224 func legendActiveFilters(activeFilters []string) []string {
1225 legendActiveFilters := make([]string, len(activeFilters)+1)
1226 legendActiveFilters[0] = "Active filters:"
1227 for i, s := range activeFilters {
1228 if len(s) > 80 {
1229 s = s[:80] + "…"
1230 }
1231 legendActiveFilters[i+1] = " " + s
1232 }
1233 return legendActiveFilters
1234 }
1235
1236 func genLabel(d int, n, l, f string) string {
1237 if d > 1 {
1238 n = n + "s"
1239 }
1240 return fmt.Sprintf("Dropped %d %s (%s <= %s)", d, n, l, f)
1241 }
1242
1243
1244
1245 func New(prof *profile.Profile, o *Options) *Report {
1246 format := func(v int64) string {
1247 if r := o.Ratio; r > 0 && r != 1 {
1248 fv := float64(v) * r
1249 v = int64(fv)
1250 }
1251 return measurement.ScaledLabel(v, o.SampleUnit, o.OutputUnit)
1252 }
1253 return &Report{prof, computeTotal(prof, o.SampleValue, o.SampleMeanDivisor),
1254 o, format}
1255 }
1256
1257
1258
1259 func NewDefault(prof *profile.Profile, options Options) *Report {
1260 index := len(prof.SampleType) - 1
1261 o := &options
1262 if o.Title == "" && len(prof.Mapping) > 0 && prof.Mapping[0].File != "" {
1263 o.Title = filepath.Base(prof.Mapping[0].File)
1264 }
1265 o.SampleType = prof.SampleType[index].Type
1266 o.SampleUnit = strings.ToLower(prof.SampleType[index].Unit)
1267 o.SampleValue = func(v []int64) int64 {
1268 return v[index]
1269 }
1270 return New(prof, o)
1271 }
1272
1273
1274
1275
1276 func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) int64 {
1277 var div, total, diffDiv, diffTotal int64
1278 for _, sample := range prof.Sample {
1279 var d, v int64
1280 v = value(sample.Value)
1281 if meanDiv != nil {
1282 d = meanDiv(sample.Value)
1283 }
1284 if v < 0 {
1285 v = -v
1286 }
1287 total += v
1288 div += d
1289 if sample.DiffBaseSample() {
1290 diffTotal += v
1291 diffDiv += d
1292 }
1293 }
1294 if diffTotal > 0 {
1295 total = diffTotal
1296 div = diffDiv
1297 }
1298 if div != 0 {
1299 return total / div
1300 }
1301 return total
1302 }
1303
1304
1305
1306 type Report struct {
1307 prof *profile.Profile
1308 total int64
1309 options *Options
1310 formatValue func(int64) string
1311 }
1312
1313
1314 func (rpt *Report) Total() int64 { return rpt.total }
1315
1316 func abs64(i int64) int64 {
1317 if i < 0 {
1318 return -i
1319 }
1320 return i
1321 }
1322
View as plain text