1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package report
16
17
18
19
20 import (
21 "bufio"
22 "fmt"
23 "html/template"
24 "io"
25 "os"
26 "path/filepath"
27 "regexp"
28 "sort"
29 "strconv"
30 "strings"
31
32 "github.com/google/pprof/internal/graph"
33 "github.com/google/pprof/internal/measurement"
34 "github.com/google/pprof/internal/plugin"
35 "github.com/google/pprof/profile"
36 )
37
38
39
40
41
42 func printSource(w io.Writer, rpt *Report) error {
43 o := rpt.options
44 g := rpt.newGraph(nil)
45
46
47
48 var functions graph.Nodes
49 functionNodes := make(map[string]graph.Nodes)
50 for _, n := range g.Nodes {
51 if !o.Symbol.MatchString(n.Info.Name) {
52 continue
53 }
54 if functionNodes[n.Info.Name] == nil {
55 functions = append(functions, n)
56 }
57 functionNodes[n.Info.Name] = append(functionNodes[n.Info.Name], n)
58 }
59 functions.Sort(graph.NameOrder)
60
61 if len(functionNodes) == 0 {
62 return fmt.Errorf("no matches found for regexp: %s", o.Symbol)
63 }
64
65 sourcePath := o.SourcePath
66 if sourcePath == "" {
67 wd, err := os.Getwd()
68 if err != nil {
69 return fmt.Errorf("could not stat current dir: %v", err)
70 }
71 sourcePath = wd
72 }
73 reader := newSourceReader(sourcePath, o.TrimPath)
74
75 fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
76 for _, fn := range functions {
77 name := fn.Info.Name
78
79
80
81 var sourceFiles graph.Nodes
82 fileNodes := make(map[string]graph.Nodes)
83 for _, n := range functionNodes[name] {
84 if n.Info.File == "" {
85 continue
86 }
87 if fileNodes[n.Info.File] == nil {
88 sourceFiles = append(sourceFiles, n)
89 }
90 fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
91 }
92
93 if len(sourceFiles) == 0 {
94 fmt.Fprintf(w, "No source information for %s\n", name)
95 continue
96 }
97
98 sourceFiles.Sort(graph.FileOrder)
99
100
101 for _, fl := range sourceFiles {
102 filename := fl.Info.File
103 fns := fileNodes[filename]
104 flatSum, cumSum := fns.Sum()
105
106 fnodes, _, err := getSourceFromFile(filename, reader, fns, 0, 0)
107 fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
108 fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
109 rpt.formatValue(flatSum), rpt.formatValue(cumSum),
110 measurement.Percentage(cumSum, rpt.total))
111
112 if err != nil {
113 fmt.Fprintf(w, " Error: %v\n", err)
114 continue
115 }
116
117 for _, fn := range fnodes {
118 fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)
119 }
120 }
121 }
122 return nil
123 }
124
125
126
127 func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
128 printHeader(w, rpt)
129 if err := PrintWebList(w, rpt, obj, -1); err != nil {
130 return err
131 }
132 printPageClosing(w)
133 return nil
134 }
135
136
137 type sourcePrinter struct {
138 reader *sourceReader
139 synth *synthCode
140 objectTool plugin.ObjTool
141 objects map[string]plugin.ObjFile
142 sym *regexp.Regexp
143 files map[string]*sourceFile
144 insts map[uint64]instructionInfo
145
146
147
148 interest map[string]bool
149
150
151 prettyNames map[string]string
152 }
153
154
155 type addrInfo struct {
156 loc *profile.Location
157 obj plugin.ObjFile
158 }
159
160
161 type instructionInfo struct {
162 objAddr uint64
163 length int
164 disasm string
165 file string
166 line int
167 flat, cum int64
168 }
169
170
171 type sourceFile struct {
172 fname string
173 cum int64
174 flat int64
175 lines map[int][]sourceInst
176 funcName map[int]string
177 }
178
179
180 type sourceInst struct {
181 addr uint64
182 stack []callID
183 }
184
185
186
187 type sourceFunction struct {
188 name string
189 begin, end int
190 flat, cum int64
191 }
192
193
194 type addressRange struct {
195 begin, end uint64
196 obj plugin.ObjFile
197 mapping *profile.Mapping
198 score int64
199 }
200
201
202
203 func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) error {
204 sourcePath := rpt.options.SourcePath
205 if sourcePath == "" {
206 wd, err := os.Getwd()
207 if err != nil {
208 return fmt.Errorf("could not stat current dir: %v", err)
209 }
210 sourcePath = wd
211 }
212 sp := newSourcePrinter(rpt, obj, sourcePath)
213 if len(sp.interest) == 0 {
214 return fmt.Errorf("no matches found for regexp: %s", rpt.options.Symbol)
215 }
216 sp.print(w, maxFiles, rpt)
217 sp.close()
218 return nil
219 }
220
221 func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter {
222 sp := &sourcePrinter{
223 reader: newSourceReader(sourcePath, rpt.options.TrimPath),
224 synth: newSynthCode(rpt.prof.Mapping),
225 objectTool: obj,
226 objects: map[string]plugin.ObjFile{},
227 sym: rpt.options.Symbol,
228 files: map[string]*sourceFile{},
229 insts: map[uint64]instructionInfo{},
230 prettyNames: map[string]string{},
231 interest: map[string]bool{},
232 }
233
234
235
236 var address *uint64
237 if sp.sym != nil {
238 if hex, err := strconv.ParseUint(sp.sym.String(), 0, 64); err == nil {
239 address = &hex
240 }
241 }
242
243 addrs := map[uint64]addrInfo{}
244 flat := map[uint64]int64{}
245 cum := map[uint64]int64{}
246
247
248 markInterest := func(addr uint64, loc *profile.Location, index int) {
249 fn := loc.Line[index]
250 if fn.Function == nil {
251 return
252 }
253 sp.interest[fn.Function.Name] = true
254 sp.interest[fn.Function.SystemName] = true
255 if _, ok := addrs[addr]; !ok {
256 addrs[addr] = addrInfo{loc, sp.objectFile(loc.Mapping)}
257 }
258 }
259
260
261 matches := func(line profile.Line) bool {
262 if line.Function == nil {
263 return false
264 }
265 return sp.sym.MatchString(line.Function.Name) ||
266 sp.sym.MatchString(line.Function.SystemName) ||
267 sp.sym.MatchString(line.Function.Filename)
268 }
269
270
271 for _, sample := range rpt.prof.Sample {
272 value := rpt.options.SampleValue(sample.Value)
273 if rpt.options.SampleMeanDivisor != nil {
274 div := rpt.options.SampleMeanDivisor(sample.Value)
275 if div != 0 {
276 value /= div
277 }
278 }
279
280
281 for i := len(sample.Location) - 1; i >= 0; i-- {
282 loc := sample.Location[i]
283 for _, line := range loc.Line {
284 if line.Function == nil {
285 continue
286 }
287 sp.prettyNames[line.Function.SystemName] = line.Function.Name
288 }
289
290 addr := loc.Address
291 if addr == 0 {
292
293 addr = sp.synth.address(loc)
294 }
295
296 cum[addr] += value
297 if i == 0 {
298 flat[addr] += value
299 }
300
301 if sp.sym == nil || (address != nil && addr == *address) {
302
303 if len(loc.Line) > 0 {
304 markInterest(addr, loc, len(loc.Line)-1)
305 }
306 continue
307 }
308
309
310 matchFile := (loc.Mapping != nil && sp.sym.MatchString(loc.Mapping.File))
311 for j, line := range loc.Line {
312 if (j == 0 && matchFile) || matches(line) {
313 markInterest(addr, loc, j)
314 }
315 }
316 }
317 }
318
319 sp.expandAddresses(rpt, addrs, flat)
320 sp.initSamples(flat, cum)
321 return sp
322 }
323
324 func (sp *sourcePrinter) close() {
325 for _, objFile := range sp.objects {
326 if objFile != nil {
327 objFile.Close()
328 }
329 }
330 }
331
332 func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]addrInfo, flat map[uint64]int64) {
333
334
335 ranges, unprocessed := sp.splitIntoRanges(rpt.prof, addrs, flat)
336 sp.handleUnprocessed(addrs, unprocessed)
337
338
339 const maxRanges = 25
340 sort.Slice(ranges, func(i, j int) bool {
341 return ranges[i].score > ranges[j].score
342 })
343 if len(ranges) > maxRanges {
344 ranges = ranges[:maxRanges]
345 }
346
347 for _, r := range ranges {
348 objBegin, err := r.obj.ObjAddr(r.begin)
349 if err != nil {
350 fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range start %x: %v\n", r.begin, err)
351 continue
352 }
353 objEnd, err := r.obj.ObjAddr(r.end)
354 if err != nil {
355 fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range end %x: %v\n", r.end, err)
356 continue
357 }
358 base := r.begin - objBegin
359 insts, err := sp.objectTool.Disasm(r.mapping.File, objBegin, objEnd, rpt.options.IntelSyntax)
360 if err != nil {
361
362 continue
363 }
364
365 var lastFrames []plugin.Frame
366 var lastAddr, maxAddr uint64
367 for i, inst := range insts {
368 addr := inst.Addr + base
369
370
371 if addr <= maxAddr {
372 continue
373 }
374 maxAddr = addr
375
376 length := 1
377 if i+1 < len(insts) && insts[i+1].Addr > inst.Addr {
378
379 length = int(insts[i+1].Addr - inst.Addr)
380 }
381
382
383 frames, err := r.obj.SourceLine(addr)
384 if err != nil {
385
386 frames = []plugin.Frame{{Func: inst.Function, File: inst.File, Line: inst.Line}}
387 }
388
389 x := instructionInfo{objAddr: inst.Addr, length: length, disasm: inst.Text}
390 if len(frames) > 0 {
391
392
393
394
395
396
397
398
399
400
401
402
403
404 index := 0
405 x.file = frames[index].File
406 x.line = frames[index].Line
407 }
408 sp.insts[addr] = x
409
410
411
412
413 const neighborhood = 32
414 if len(frames) > 0 && frames[0].Line != 0 {
415 lastFrames = frames
416 lastAddr = addr
417 } else if (addr-lastAddr <= neighborhood) && lastFrames != nil {
418 frames = lastFrames
419 }
420
421 sp.addStack(addr, frames)
422 }
423 }
424 }
425
426 func (sp *sourcePrinter) addStack(addr uint64, frames []plugin.Frame) {
427
428 for i, f := range frames {
429 if !sp.interest[f.Func] {
430 continue
431 }
432
433
434 fname := canonicalizeFileName(f.File)
435 file := sp.files[fname]
436 if file == nil {
437 file = &sourceFile{
438 fname: fname,
439 lines: map[int][]sourceInst{},
440 funcName: map[int]string{},
441 }
442 sp.files[fname] = file
443 }
444 callees := frames[:i]
445 stack := make([]callID, 0, len(callees))
446 for j := len(callees) - 1; j >= 0; j-- {
447 stack = append(stack, callID{
448 file: callees[j].File,
449 line: callees[j].Line,
450 })
451 }
452 file.lines[f.Line] = append(file.lines[f.Line], sourceInst{addr, stack})
453
454
455
456 if _, ok := file.funcName[f.Line]; !ok {
457 file.funcName[f.Line] = f.Func
458 }
459 }
460 }
461
462
463 const synthAsm = ""
464
465
466
467 func (sp *sourcePrinter) handleUnprocessed(addrs map[uint64]addrInfo, unprocessed []uint64) {
468
469
470
471 makeFrames := func(addr uint64) []plugin.Frame {
472 loc := addrs[addr].loc
473 stack := make([]plugin.Frame, 0, len(loc.Line))
474 for _, line := range loc.Line {
475 fn := line.Function
476 if fn == nil {
477 continue
478 }
479 stack = append(stack, plugin.Frame{
480 Func: fn.Name,
481 File: fn.Filename,
482 Line: int(line.Line),
483 })
484 }
485 return stack
486 }
487
488 for _, addr := range unprocessed {
489 frames := makeFrames(addr)
490 x := instructionInfo{
491 objAddr: addr,
492 length: 1,
493 disasm: synthAsm,
494 }
495 if len(frames) > 0 {
496 x.file = frames[0].File
497 x.line = frames[0].Line
498 }
499 sp.insts[addr] = x
500
501 sp.addStack(addr, frames)
502 }
503 }
504
505
506
507
508 func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, addrMap map[uint64]addrInfo, flat map[uint64]int64) ([]addressRange, []uint64) {
509
510 var addrs, unprocessed []uint64
511 for addr, info := range addrMap {
512 if info.obj != nil {
513 addrs = append(addrs, addr)
514 } else {
515 unprocessed = append(unprocessed, addr)
516 }
517 }
518 sort.Slice(addrs, func(i, j int) bool { return addrs[i] < addrs[j] })
519
520 const expand = 500
521 var result []addressRange
522 for i, n := 0, len(addrs); i < n; {
523 begin, end := addrs[i], addrs[i]
524 sum := flat[begin]
525 i++
526
527 info := addrMap[begin]
528 m := info.loc.Mapping
529 obj := info.obj
530
531
532 for i < n && addrs[i] <= end+2*expand && addrs[i] < m.Limit {
533
534
535 end = addrs[i]
536 sum += flat[end]
537 i++
538 }
539 if m.Start-begin >= expand {
540 begin -= expand
541 } else {
542 begin = m.Start
543 }
544 if m.Limit-end >= expand {
545 end += expand
546 } else {
547 end = m.Limit
548 }
549
550 result = append(result, addressRange{begin, end, obj, m, sum})
551 }
552 return result, unprocessed
553 }
554
555 func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) {
556 for addr, inst := range sp.insts {
557
558
559
560 instEnd := addr + uint64(inst.length)
561 for p := addr; p < instEnd; p++ {
562 inst.flat += flat[p]
563 inst.cum += cum[p]
564 }
565 sp.insts[addr] = inst
566 }
567 }
568
569 func (sp *sourcePrinter) print(w io.Writer, maxFiles int, rpt *Report) {
570
571 for _, file := range sp.files {
572 seen := map[uint64]bool{}
573 for _, line := range file.lines {
574 for _, x := range line {
575 if seen[x.addr] {
576
577
578
579 continue
580 }
581 seen[x.addr] = true
582 inst := sp.insts[x.addr]
583 file.cum += inst.cum
584 file.flat += inst.flat
585 }
586 }
587 }
588
589
590 var files []*sourceFile
591 for _, f := range sp.files {
592 files = append(files, f)
593 }
594 order := func(i, j int) bool { return files[i].flat > files[j].flat }
595 if maxFiles < 0 {
596
597 order = func(i, j int) bool { return files[i].fname < files[j].fname }
598 maxFiles = len(files)
599 }
600 sort.Slice(files, order)
601 for i, f := range files {
602 if i < maxFiles {
603 sp.printFile(w, f, rpt)
604 }
605 }
606 }
607
608 func (sp *sourcePrinter) printFile(w io.Writer, f *sourceFile, rpt *Report) {
609 for _, fn := range sp.functions(f) {
610 if fn.cum == 0 {
611 continue
612 }
613 printFunctionHeader(w, fn.name, f.fname, fn.flat, fn.cum, rpt)
614 var asm []assemblyInstruction
615 for l := fn.begin; l < fn.end; l++ {
616 lineContents, ok := sp.reader.line(f.fname, l)
617 if !ok {
618 if len(f.lines[l]) == 0 {
619
620 continue
621 }
622 if l == 0 {
623
624 lineContents = "<instructions with unknown line numbers>"
625 } else {
626
627 lineContents = "???"
628 }
629 }
630
631
632 asm = asm[:0]
633 var flatSum, cumSum int64
634 var lastAddr uint64
635 for _, inst := range f.lines[l] {
636 addr := inst.addr
637 x := sp.insts[addr]
638 flatSum += x.flat
639 cumSum += x.cum
640 startsBlock := (addr != lastAddr+uint64(sp.insts[lastAddr].length))
641 lastAddr = addr
642
643
644 asm = append(asm, assemblyInstruction{
645 address: x.objAddr,
646 instruction: x.disasm,
647 function: fn.name,
648 file: x.file,
649 line: x.line,
650 flat: x.flat,
651 cum: x.cum,
652 startsBlock: startsBlock,
653 inlineCalls: inst.stack,
654 })
655 }
656
657 printFunctionSourceLine(w, l, flatSum, cumSum, lineContents, asm, sp.reader, rpt)
658 }
659 printFunctionClosing(w)
660 }
661 }
662
663
664 func (sp *sourcePrinter) functions(f *sourceFile) []sourceFunction {
665 var funcs []sourceFunction
666
667
668 lines := make([]int, 0, len(f.lines))
669 for l := range f.lines {
670 lines = append(lines, l)
671 }
672 sort.Ints(lines)
673
674
675 const mergeLimit = 20
676 for _, l := range lines {
677 name := f.funcName[l]
678 if pretty, ok := sp.prettyNames[name]; ok {
679
680 name = pretty
681 }
682
683 fn := sourceFunction{name: name, begin: l, end: l + 1}
684 for _, x := range f.lines[l] {
685 inst := sp.insts[x.addr]
686 fn.flat += inst.flat
687 fn.cum += inst.cum
688 }
689
690
691 if len(funcs) > 0 {
692 last := funcs[len(funcs)-1]
693 if l-last.end < mergeLimit && last.name == name {
694 last.end = l + 1
695 last.flat += fn.flat
696 last.cum += fn.cum
697 funcs[len(funcs)-1] = last
698 continue
699 }
700 }
701
702
703 funcs = append(funcs, fn)
704 }
705
706
707 const expand = 5
708 for i, f := range funcs {
709 if i == 0 {
710
711
712
713 if f.begin > expand {
714 f.begin -= expand
715 } else if f.begin > 1 {
716 f.begin = 1
717 }
718 } else {
719
720 halfGap := (f.begin - funcs[i-1].end) / 2
721 if halfGap > expand {
722 halfGap = expand
723 }
724 funcs[i-1].end += halfGap
725 f.begin -= halfGap
726 }
727 funcs[i] = f
728 }
729
730
731 if len(funcs) > 0 {
732 funcs[len(funcs)-1].end += expand
733 }
734
735 return funcs
736 }
737
738
739
740 func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile {
741 if m == nil {
742 return nil
743 }
744 if object, ok := sp.objects[m.File]; ok {
745 return object
746 }
747 object, err := sp.objectTool.Open(m.File, m.Start, m.Limit, m.Offset)
748 if err != nil {
749 object = nil
750 }
751 sp.objects[m.File] = object
752 return object
753 }
754
755
756 func printHeader(w io.Writer, rpt *Report) {
757 fmt.Fprintln(w, `
758 <!DOCTYPE html>
759 <html>
760 <head>
761 <meta charset="UTF-8">
762 <title>Pprof listing</title>`)
763 fmt.Fprintln(w, weblistPageCSS)
764 fmt.Fprintln(w, weblistPageScript)
765 fmt.Fprint(w, "</head>\n<body>\n\n")
766
767 var labels []string
768 for _, l := range ProfileLabels(rpt) {
769 labels = append(labels, template.HTMLEscapeString(l))
770 }
771
772 fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
773 strings.Join(labels, "<br>\n"),
774 rpt.formatValue(rpt.total),
775 )
776 }
777
778
779 func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
780 fmt.Fprintf(w, `<h2>%s</h2><p class="filename">%s</p>
781 <pre onClick="pprof_toggle_asm(event)">
782 Total: %10s %10s (flat, cum) %s
783 `,
784 template.HTMLEscapeString(name), template.HTMLEscapeString(path),
785 rpt.formatValue(flatSum), rpt.formatValue(cumSum),
786 measurement.Percentage(cumSum, rpt.total))
787 }
788
789
790 func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineContents string,
791 assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
792 if len(assembly) == 0 {
793 fmt.Fprintf(w,
794 "<span class=line> %6d</span> <span class=nop> %10s %10s %8s %s </span>\n",
795 lineNo,
796 valueOrDot(flat, rpt), valueOrDot(cum, rpt),
797 "", template.HTMLEscapeString(lineContents))
798 return
799 }
800
801 nestedInfo := false
802 cl := "deadsrc"
803 for _, an := range assembly {
804 if len(an.inlineCalls) > 0 || an.instruction != synthAsm {
805 nestedInfo = true
806 cl = "livesrc"
807 }
808 }
809
810 fmt.Fprintf(w,
811 "<span class=line> %6d</span> <span class=%s> %10s %10s %8s %s </span>",
812 lineNo, cl,
813 valueOrDot(flat, rpt), valueOrDot(cum, rpt),
814 "", template.HTMLEscapeString(lineContents))
815 if nestedInfo {
816 srcIndent := indentation(lineContents)
817 printNested(w, srcIndent, assembly, reader, rpt)
818 }
819 fmt.Fprintln(w)
820 }
821
822 func printNested(w io.Writer, srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
823 fmt.Fprint(w, "<span class=asm>")
824 var curCalls []callID
825 for i, an := range assembly {
826 if an.startsBlock && i != 0 {
827
828 fmt.Fprintf(w, " %8s %28s\n", "", "⋮")
829 }
830
831 var fileline string
832 if an.file != "" {
833 fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(filepath.Base(an.file)), an.line)
834 }
835 flat, cum := an.flat, an.cum
836
837
838 for j, c := range an.inlineCalls {
839 if j < len(curCalls) && curCalls[j] == c {
840
841 continue
842 }
843 curCalls = nil
844 fline, ok := reader.line(c.file, c.line)
845 if !ok {
846 fline = ""
847 }
848 text := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline)
849 fmt.Fprintf(w, " %8s %10s %10s %8s <span class=inlinesrc>%s</span> <span class=unimportant>%s:%d</span>\n",
850 "", "", "", "",
851 template.HTMLEscapeString(rightPad(text, 80)),
852 template.HTMLEscapeString(filepath.Base(c.file)), c.line)
853 }
854 curCalls = an.inlineCalls
855 if an.instruction == synthAsm {
856 continue
857 }
858 text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
859 fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
860 "", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address,
861 template.HTMLEscapeString(rightPad(text, 80)),
862
863
864
865 fileline)
866 }
867 fmt.Fprint(w, "</span>")
868 }
869
870
871 func printFunctionClosing(w io.Writer) {
872 fmt.Fprintln(w, "</pre>")
873 }
874
875
876 func printPageClosing(w io.Writer) {
877 fmt.Fprintln(w, weblistPageClosing)
878 }
879
880
881
882
883 func getSourceFromFile(file string, reader *sourceReader, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
884 lineNodes := make(map[int]graph.Nodes)
885
886
887 const margin = 5
888 if start == 0 {
889 if fns[0].Info.StartLine != 0 {
890 start = fns[0].Info.StartLine
891 } else {
892 start = fns[0].Info.Lineno - margin
893 }
894 } else {
895 start -= margin
896 }
897 if end == 0 {
898 end = fns[0].Info.Lineno
899 }
900 end += margin
901 for _, n := range fns {
902 lineno := n.Info.Lineno
903 nodeStart := n.Info.StartLine
904 if nodeStart == 0 {
905 nodeStart = lineno - margin
906 }
907 nodeEnd := lineno + margin
908 if nodeStart < start {
909 start = nodeStart
910 } else if nodeEnd > end {
911 end = nodeEnd
912 }
913 lineNodes[lineno] = append(lineNodes[lineno], n)
914 }
915 if start < 1 {
916 start = 1
917 }
918
919 var src graph.Nodes
920 for lineno := start; lineno <= end; lineno++ {
921 line, ok := reader.line(file, lineno)
922 if !ok {
923 break
924 }
925 flat, cum := lineNodes[lineno].Sum()
926 src = append(src, &graph.Node{
927 Info: graph.NodeInfo{
928 Name: strings.TrimRight(line, "\n"),
929 Lineno: lineno,
930 },
931 Flat: flat,
932 Cum: cum,
933 })
934 }
935 if err := reader.fileError(file); err != nil {
936 return nil, file, err
937 }
938 return src, file, nil
939 }
940
941
942 type sourceReader struct {
943
944
945 searchPath string
946
947
948 trimPath string
949
950
951
952 files map[string][]string
953
954
955
956 errors map[string]error
957 }
958
959 func newSourceReader(searchPath, trimPath string) *sourceReader {
960 return &sourceReader{
961 searchPath,
962 trimPath,
963 make(map[string][]string),
964 make(map[string]error),
965 }
966 }
967
968 func (reader *sourceReader) fileError(path string) error {
969 return reader.errors[path]
970 }
971
972
973 func (reader *sourceReader) line(path string, lineno int) (string, bool) {
974 lines, ok := reader.files[path]
975 if !ok {
976
977 lines = []string{""}
978 f, err := openSourceFile(path, reader.searchPath, reader.trimPath)
979 if err != nil {
980 reader.errors[path] = err
981 } else {
982 s := bufio.NewScanner(f)
983 for s.Scan() {
984 lines = append(lines, s.Text())
985 }
986 f.Close()
987 if s.Err() != nil {
988 reader.errors[path] = err
989 }
990 }
991 reader.files[path] = lines
992 }
993 if lineno <= 0 || lineno >= len(lines) {
994 return "", false
995 }
996 return lines[lineno], true
997 }
998
999
1000
1001
1002
1003
1004
1005 func openSourceFile(path, searchPath, trim string) (*os.File, error) {
1006 path = trimPath(path, trim, searchPath)
1007
1008 if filepath.IsAbs(path) {
1009 f, err := os.Open(path)
1010 return f, err
1011 }
1012
1013 for _, dir := range filepath.SplitList(searchPath) {
1014
1015 for {
1016 filename := filepath.Join(dir, path)
1017 if f, err := os.Open(filename); err == nil {
1018 return f, nil
1019 }
1020 parent := filepath.Dir(dir)
1021 if parent == dir {
1022 break
1023 }
1024 dir = parent
1025 }
1026 }
1027
1028 return nil, fmt.Errorf("could not find file %s on path %s", path, searchPath)
1029 }
1030
1031
1032
1033
1034
1035 func trimPath(path, trimPath, searchPath string) string {
1036
1037 sPath, searchPath := filepath.ToSlash(path), filepath.ToSlash(searchPath)
1038 if trimPath == "" {
1039
1040
1041
1042
1043
1044
1045 for _, dir := range filepath.SplitList(searchPath) {
1046 want := "/" + filepath.Base(dir) + "/"
1047 if found := strings.Index(sPath, want); found != -1 {
1048 return path[found+len(want):]
1049 }
1050 }
1051 }
1052
1053 trimPaths := append(filepath.SplitList(filepath.ToSlash(trimPath)), "/proc/self/cwd/./", "/proc/self/cwd/")
1054 for _, trimPath := range trimPaths {
1055 if !strings.HasSuffix(trimPath, "/") {
1056 trimPath += "/"
1057 }
1058 if strings.HasPrefix(sPath, trimPath) {
1059 return path[len(trimPath):]
1060 }
1061 }
1062 return path
1063 }
1064
1065 func indentation(line string) int {
1066 column := 0
1067 for _, c := range line {
1068 if c == ' ' {
1069 column++
1070 } else if c == '\t' {
1071 column++
1072 for column%8 != 0 {
1073 column++
1074 }
1075 } else {
1076 break
1077 }
1078 }
1079 return column
1080 }
1081
1082
1083
1084
1085 func rightPad(s string, n int) string {
1086 var str strings.Builder
1087
1088
1089
1090 column := 0
1091 for _, c := range s {
1092 column++
1093 if c == '\t' {
1094 str.WriteRune(' ')
1095 for column%8 != 0 {
1096 column++
1097 str.WriteRune(' ')
1098 }
1099 } else {
1100 str.WriteRune(c)
1101 }
1102 }
1103 for column < n {
1104 column++
1105 str.WriteRune(' ')
1106 }
1107 return str.String()
1108 }
1109
1110 func canonicalizeFileName(fname string) string {
1111 fname = strings.TrimPrefix(fname, "/proc/self/cwd/")
1112 fname = strings.TrimPrefix(fname, "./")
1113 return filepath.Clean(fname)
1114 }
1115
View as plain text