暂无描述

source.go 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. // Copyright 2014 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package report
  15. // This file contains routines related to the generation of annotated
  16. // source listings.
  17. import (
  18. "bufio"
  19. "fmt"
  20. "html/template"
  21. "io"
  22. "os"
  23. "path/filepath"
  24. "strconv"
  25. "strings"
  26. "github.com/google/pprof/internal/graph"
  27. "github.com/google/pprof/internal/plugin"
  28. )
  29. // printSource prints an annotated source listing, include all
  30. // functions with samples that match the regexp rpt.options.symbol.
  31. // The sources are sorted by function name and then by filename to
  32. // eliminate potential nondeterminism.
  33. func printSource(w io.Writer, rpt *Report) error {
  34. o := rpt.options
  35. g := rpt.newGraph(nil)
  36. // Identify all the functions that match the regexp provided.
  37. // Group nodes for each matching function.
  38. var functions graph.Nodes
  39. functionNodes := make(map[string]graph.Nodes)
  40. for _, n := range g.Nodes {
  41. if !o.Symbol.MatchString(n.Info.Name) {
  42. continue
  43. }
  44. if functionNodes[n.Info.Name] == nil {
  45. functions = append(functions, n)
  46. }
  47. functionNodes[n.Info.Name] = append(functionNodes[n.Info.Name], n)
  48. }
  49. functions.Sort(graph.NameOrder)
  50. sourcePath := o.SourcePath
  51. if sourcePath == "" {
  52. wd, err := os.Getwd()
  53. if err != nil {
  54. return fmt.Errorf("Could not stat current dir: %v", err)
  55. }
  56. sourcePath = wd
  57. }
  58. reader := newSourceReader(sourcePath)
  59. fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
  60. for _, fn := range functions {
  61. name := fn.Info.Name
  62. // Identify all the source files associated to this function.
  63. // Group nodes for each source file.
  64. var sourceFiles graph.Nodes
  65. fileNodes := make(map[string]graph.Nodes)
  66. for _, n := range functionNodes[name] {
  67. if n.Info.File == "" {
  68. continue
  69. }
  70. if fileNodes[n.Info.File] == nil {
  71. sourceFiles = append(sourceFiles, n)
  72. }
  73. fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
  74. }
  75. if len(sourceFiles) == 0 {
  76. fmt.Fprintf(w, "No source information for %s\n", name)
  77. continue
  78. }
  79. sourceFiles.Sort(graph.FileOrder)
  80. // Print each file associated with this function.
  81. for _, fl := range sourceFiles {
  82. filename := fl.Info.File
  83. fns := fileNodes[filename]
  84. flatSum, cumSum := fns.Sum()
  85. fnodes, _, err := getSourceFromFile(filename, reader, fns, 0, 0)
  86. fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
  87. fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
  88. rpt.formatValue(flatSum), rpt.formatValue(cumSum),
  89. percentage(cumSum, rpt.total))
  90. if err != nil {
  91. fmt.Fprintf(w, " Error: %v\n", err)
  92. continue
  93. }
  94. for _, fn := range fnodes {
  95. fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)
  96. }
  97. }
  98. }
  99. return nil
  100. }
  101. // printWebSource prints an annotated source listing, include all
  102. // functions with samples that match the regexp rpt.options.symbol.
  103. func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
  104. printHeader(w, rpt)
  105. if err := PrintWebList(w, rpt, obj, -1); err != nil {
  106. return err
  107. }
  108. printPageClosing(w)
  109. return nil
  110. }
  111. // PrintWebList prints annotated source listing of rpt to w.
  112. func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) error {
  113. o := rpt.options
  114. g := rpt.newGraph(nil)
  115. // If the regexp source can be parsed as an address, also match
  116. // functions that land on that address.
  117. var address *uint64
  118. if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
  119. address = &hex
  120. }
  121. sourcePath := o.SourcePath
  122. if sourcePath == "" {
  123. wd, err := os.Getwd()
  124. if err != nil {
  125. return fmt.Errorf("Could not stat current dir: %v", err)
  126. }
  127. sourcePath = wd
  128. }
  129. reader := newSourceReader(sourcePath)
  130. type fileFunction struct {
  131. fileName, functionName string
  132. }
  133. // Extract interesting symbols from binary files in the profile and
  134. // classify samples per symbol.
  135. symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj)
  136. symNodes := nodesPerSymbol(g.Nodes, symbols)
  137. // Identify sources associated to a symbol by examining
  138. // symbol samples. Classify samples per source file.
  139. fileNodes := make(map[fileFunction]graph.Nodes)
  140. if len(symNodes) == 0 {
  141. for _, n := range g.Nodes {
  142. if n.Info.File == "" || !o.Symbol.MatchString(n.Info.Name) {
  143. continue
  144. }
  145. ff := fileFunction{n.Info.File, n.Info.Name}
  146. fileNodes[ff] = append(fileNodes[ff], n)
  147. }
  148. } else {
  149. for _, nodes := range symNodes {
  150. for _, n := range nodes {
  151. if n.Info.File != "" {
  152. ff := fileFunction{n.Info.File, n.Info.Name}
  153. fileNodes[ff] = append(fileNodes[ff], n)
  154. }
  155. }
  156. }
  157. }
  158. if len(fileNodes) == 0 {
  159. return fmt.Errorf("No source information for %s", o.Symbol.String())
  160. }
  161. sourceFiles := make(graph.Nodes, 0, len(fileNodes))
  162. for _, nodes := range fileNodes {
  163. sNode := *nodes[0]
  164. sNode.Flat, sNode.Cum = nodes.Sum()
  165. sourceFiles = append(sourceFiles, &sNode)
  166. }
  167. // Limit number of files printed?
  168. if maxFiles < 0 {
  169. sourceFiles.Sort(graph.FileOrder)
  170. } else {
  171. sourceFiles.Sort(graph.FlatNameOrder)
  172. if maxFiles < len(sourceFiles) {
  173. sourceFiles = sourceFiles[:maxFiles]
  174. }
  175. }
  176. // Print each file associated with this function.
  177. for _, n := range sourceFiles {
  178. ff := fileFunction{n.Info.File, n.Info.Name}
  179. fns := fileNodes[ff]
  180. asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj)
  181. start, end := sourceCoordinates(asm)
  182. fnodes, path, err := getSourceFromFile(ff.fileName, reader, fns, start, end)
  183. if err != nil {
  184. fnodes, path = getMissingFunctionSource(ff.fileName, asm, start, end)
  185. }
  186. printFunctionHeader(w, ff.functionName, path, n.Flat, n.Cum, rpt)
  187. for _, fn := range fnodes {
  188. printFunctionSourceLine(w, fn, asm[fn.Info.Lineno], reader, rpt)
  189. }
  190. printFunctionClosing(w)
  191. }
  192. return nil
  193. }
  194. // sourceCoordinates returns the lowest and highest line numbers from
  195. // a set of assembly statements.
  196. func sourceCoordinates(asm map[int][]assemblyInstruction) (start, end int) {
  197. for l := range asm {
  198. if start == 0 || l < start {
  199. start = l
  200. }
  201. if end == 0 || l > end {
  202. end = l
  203. }
  204. }
  205. return start, end
  206. }
  207. // assemblyPerSourceLine disassembles the binary containing a symbol
  208. // and classifies the assembly instructions according to its
  209. // corresponding source line, annotating them with a set of samples.
  210. func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj plugin.ObjTool) map[int][]assemblyInstruction {
  211. assembly := make(map[int][]assemblyInstruction)
  212. // Identify symbol to use for this collection of samples.
  213. o := findMatchingSymbol(objSyms, rs)
  214. if o == nil {
  215. return assembly
  216. }
  217. // Extract assembly for matched symbol
  218. insts, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End)
  219. if err != nil {
  220. return assembly
  221. }
  222. srcBase := filepath.Base(src)
  223. anodes := annotateAssembly(insts, rs, o.base)
  224. var lineno = 0
  225. var prevline = 0
  226. for _, an := range anodes {
  227. // Do not rely solely on the line number produced by Disasm
  228. // since it is not what we want in the presence of inlining.
  229. //
  230. // E.g., suppose we are printing source code for F and this
  231. // instruction is from H where F called G called H and both
  232. // of those calls were inlined. We want to use the line
  233. // number from F, not from H (which is what Disasm gives us).
  234. //
  235. // So find the outer-most linenumber in the source file.
  236. found := false
  237. if frames, err := o.file.SourceLine(an.address + o.base); err == nil {
  238. for i := len(frames) - 1; i >= 0; i-- {
  239. if filepath.Base(frames[i].File) == srcBase {
  240. for j := i - 1; j >= 0; j-- {
  241. an.inlineCalls = append(an.inlineCalls, callID{frames[j].File, frames[j].Line})
  242. }
  243. lineno = frames[i].Line
  244. found = true
  245. break
  246. }
  247. }
  248. }
  249. if !found && filepath.Base(an.file) == srcBase {
  250. lineno = an.line
  251. }
  252. if lineno != 0 {
  253. if lineno != prevline {
  254. // This instruction starts a new block
  255. // of contiguous instructions on this line.
  256. an.startsBlock = true
  257. }
  258. prevline = lineno
  259. assembly[lineno] = append(assembly[lineno], an)
  260. }
  261. }
  262. return assembly
  263. }
  264. // findMatchingSymbol looks for the symbol that corresponds to a set
  265. // of samples, by comparing their addresses.
  266. func findMatchingSymbol(objSyms []*objSymbol, ns graph.Nodes) *objSymbol {
  267. for _, n := range ns {
  268. for _, o := range objSyms {
  269. if filepath.Base(o.sym.File) == filepath.Base(n.Info.Objfile) &&
  270. o.sym.Start <= n.Info.Address-o.base &&
  271. n.Info.Address-o.base <= o.sym.End {
  272. return o
  273. }
  274. }
  275. }
  276. return nil
  277. }
  278. // printHeader prints the page header for a weblist report.
  279. func printHeader(w io.Writer, rpt *Report) {
  280. fmt.Fprintln(w, `
  281. <!DOCTYPE html>
  282. <html>
  283. <head>
  284. <meta charset="UTF-8">
  285. <title>Pprof listing</title>`)
  286. fmt.Fprintln(w, weblistPageCSS)
  287. fmt.Fprintln(w, weblistPageScript)
  288. fmt.Fprint(w, "</head>\n<body>\n\n")
  289. var labels []string
  290. for _, l := range ProfileLabels(rpt) {
  291. labels = append(labels, template.HTMLEscapeString(l))
  292. }
  293. fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
  294. strings.Join(labels, "<br>\n"),
  295. rpt.formatValue(rpt.total),
  296. )
  297. }
  298. // printFunctionHeader prints a function header for a weblist report.
  299. func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
  300. fmt.Fprintf(w, `<h1>%s</h1>%s
  301. <pre onClick="pprof_toggle_asm(event)">
  302. Total: %10s %10s (flat, cum) %s
  303. `,
  304. template.HTMLEscapeString(name), template.HTMLEscapeString(path),
  305. rpt.formatValue(flatSum), rpt.formatValue(cumSum),
  306. percentage(cumSum, rpt.total))
  307. }
  308. // printFunctionSourceLine prints a source line and the corresponding assembly.
  309. func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
  310. if len(assembly) == 0 {
  311. fmt.Fprintf(w,
  312. "<span class=line> %6d</span> <span class=nop> %10s %10s %8s %s </span>\n",
  313. fn.Info.Lineno,
  314. valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
  315. "", template.HTMLEscapeString(fn.Info.Name))
  316. return
  317. }
  318. fmt.Fprintf(w,
  319. "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %8s %s </span>",
  320. fn.Info.Lineno,
  321. valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
  322. "", template.HTMLEscapeString(fn.Info.Name))
  323. srcIndent := indentation(fn.Info.Name)
  324. fmt.Fprint(w, "<span class=asm>")
  325. var curCalls []callID
  326. for i, an := range assembly {
  327. if an.startsBlock && i != 0 {
  328. // Insert a separator between discontiguous blocks.
  329. fmt.Fprintf(w, " %8s %28s\n", "", "⋮")
  330. }
  331. var fileline string
  332. if an.file != "" {
  333. fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.file), an.line)
  334. }
  335. flat, cum := an.flat, an.cum
  336. if an.flatDiv != 0 {
  337. flat = flat / an.flatDiv
  338. }
  339. if an.cumDiv != 0 {
  340. cum = cum / an.cumDiv
  341. }
  342. // Print inlined call context.
  343. for j, c := range an.inlineCalls {
  344. if j < len(curCalls) && curCalls[j] == c {
  345. // Skip if same as previous instruction.
  346. continue
  347. }
  348. curCalls = nil
  349. fname := trimPath(c.file)
  350. fline, ok := reader.line(fname, c.line)
  351. if !ok {
  352. fline = ""
  353. }
  354. text := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline)
  355. fmt.Fprintf(w, " %8s %10s %10s %8s <span class=inlinesrc>%s</span> <span class=unimportant>%s:%d</span>\n",
  356. "", "", "", "",
  357. template.HTMLEscapeString(fmt.Sprintf("%-80s", text)),
  358. template.HTMLEscapeString(filepath.Base(fname)), c.line)
  359. }
  360. curCalls = an.inlineCalls
  361. text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
  362. fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
  363. "", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address,
  364. template.HTMLEscapeString(fmt.Sprintf("%-80s", text)),
  365. template.HTMLEscapeString(fileline))
  366. }
  367. fmt.Fprintln(w, "</span>")
  368. }
  369. // printFunctionClosing prints the end of a function in a weblist report.
  370. func printFunctionClosing(w io.Writer) {
  371. fmt.Fprintln(w, "</pre>")
  372. }
  373. // printPageClosing prints the end of the page in a weblist report.
  374. func printPageClosing(w io.Writer) {
  375. fmt.Fprintln(w, weblistPageClosing)
  376. }
  377. // getSourceFromFile collects the sources of a function from a source
  378. // file and annotates it with the samples in fns. Returns the sources
  379. // as nodes, using the info.name field to hold the source code.
  380. func getSourceFromFile(file string, reader *sourceReader, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
  381. file = trimPath(file)
  382. lineNodes := make(map[int]graph.Nodes)
  383. // Collect source coordinates from profile.
  384. const margin = 5 // Lines before first/after last sample.
  385. if start == 0 {
  386. if fns[0].Info.StartLine != 0 {
  387. start = fns[0].Info.StartLine
  388. } else {
  389. start = fns[0].Info.Lineno - margin
  390. }
  391. } else {
  392. start -= margin
  393. }
  394. if end == 0 {
  395. end = fns[0].Info.Lineno
  396. }
  397. end += margin
  398. for _, n := range fns {
  399. lineno := n.Info.Lineno
  400. nodeStart := n.Info.StartLine
  401. if nodeStart == 0 {
  402. nodeStart = lineno - margin
  403. }
  404. nodeEnd := lineno + margin
  405. if nodeStart < start {
  406. start = nodeStart
  407. } else if nodeEnd > end {
  408. end = nodeEnd
  409. }
  410. lineNodes[lineno] = append(lineNodes[lineno], n)
  411. }
  412. if start < 1 {
  413. start = 1
  414. }
  415. var src graph.Nodes
  416. for lineno := start; lineno <= end; lineno++ {
  417. line, ok := reader.line(file, lineno)
  418. if !ok {
  419. break
  420. }
  421. flat, cum := lineNodes[lineno].Sum()
  422. src = append(src, &graph.Node{
  423. Info: graph.NodeInfo{
  424. Name: strings.TrimRight(line, "\n"),
  425. Lineno: lineno,
  426. },
  427. Flat: flat,
  428. Cum: cum,
  429. })
  430. }
  431. if err := reader.fileError(file); err != nil {
  432. return nil, file, err
  433. }
  434. return src, file, nil
  435. }
  436. // getMissingFunctionSource creates a dummy function body to point to
  437. // the source file and annotates it with the samples in asm.
  438. func getMissingFunctionSource(filename string, asm map[int][]assemblyInstruction, start, end int) (graph.Nodes, string) {
  439. var fnodes graph.Nodes
  440. for i := start; i <= end; i++ {
  441. insts := asm[i]
  442. if len(insts) == 0 {
  443. continue
  444. }
  445. var group assemblyInstruction
  446. for _, insn := range insts {
  447. group.flat += insn.flat
  448. group.cum += insn.cum
  449. group.flatDiv += insn.flatDiv
  450. group.cumDiv += insn.cumDiv
  451. }
  452. flat := group.flatValue()
  453. cum := group.cumValue()
  454. fnodes = append(fnodes, &graph.Node{
  455. Info: graph.NodeInfo{
  456. Name: "???",
  457. Lineno: i,
  458. },
  459. Flat: flat,
  460. Cum: cum,
  461. })
  462. }
  463. return fnodes, filename
  464. }
  465. // sourceReader provides access to source code with caching of file contents.
  466. type sourceReader struct {
  467. searchPath string
  468. // files maps from path name to a list of lines.
  469. // files[*][0] is unused since line numbering starts at 1.
  470. files map[string][]string
  471. // errors collects errors encountered per file. These errors are
  472. // consulted before returning out of these module.
  473. errors map[string]error
  474. }
  475. func newSourceReader(searchPath string) *sourceReader {
  476. return &sourceReader{
  477. searchPath,
  478. make(map[string][]string),
  479. make(map[string]error),
  480. }
  481. }
  482. func (reader *sourceReader) fileError(path string) error {
  483. return reader.errors[path]
  484. }
  485. func (reader *sourceReader) line(path string, lineno int) (string, bool) {
  486. lines, ok := reader.files[path]
  487. if !ok {
  488. // Read and cache file contents.
  489. lines = []string{""} // Skip 0th line
  490. f, err := openSourceFile(path, reader.searchPath)
  491. if err != nil {
  492. reader.errors[path] = err
  493. } else {
  494. s := bufio.NewScanner(f)
  495. for s.Scan() {
  496. lines = append(lines, s.Text())
  497. }
  498. f.Close()
  499. if s.Err() != nil {
  500. reader.errors[path] = err
  501. }
  502. }
  503. reader.files[path] = lines
  504. }
  505. if lineno <= 0 || lineno >= len(lines) {
  506. return "", false
  507. }
  508. return lines[lineno], true
  509. }
  510. // openSourceFile opens a source file from a name encoded in a
  511. // profile. File names in a profile after often relative paths, so
  512. // search them in each of the paths in searchPath (or CWD by default),
  513. // and their parents.
  514. func openSourceFile(path, searchPath string) (*os.File, error) {
  515. if filepath.IsAbs(path) {
  516. f, err := os.Open(path)
  517. return f, err
  518. }
  519. // Scan each component of the path
  520. for _, dir := range strings.Split(searchPath, ":") {
  521. // Search up for every parent of each possible path.
  522. for {
  523. filename := filepath.Join(dir, path)
  524. if f, err := os.Open(filename); err == nil {
  525. return f, nil
  526. }
  527. parent := filepath.Dir(dir)
  528. if parent == dir {
  529. break
  530. }
  531. dir = parent
  532. }
  533. }
  534. return nil, fmt.Errorf("Could not find file %s on path %s", path, searchPath)
  535. }
  536. // trimPath cleans up a path by removing prefixes that are commonly
  537. // found on profiles.
  538. func trimPath(path string) string {
  539. basePaths := []string{
  540. "/proc/self/cwd/./",
  541. "/proc/self/cwd/",
  542. }
  543. sPath := filepath.ToSlash(path)
  544. for _, base := range basePaths {
  545. if strings.HasPrefix(sPath, base) {
  546. return filepath.FromSlash(sPath[len(base):])
  547. }
  548. }
  549. return path
  550. }
  551. func indentation(line string) int {
  552. column := 0
  553. for _, c := range line {
  554. if c == ' ' {
  555. column++
  556. } else if c == '\t' {
  557. column++
  558. for column%8 != 0 {
  559. column++
  560. }
  561. } else {
  562. break
  563. }
  564. }
  565. return column
  566. }