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