Bez popisu

source.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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. "sort"
  25. "strconv"
  26. "strings"
  27. "github.com/google/pprof/internal/graph"
  28. "github.com/google/pprof/internal/plugin"
  29. )
  30. // printSource prints an annotated source listing, include all
  31. // functions with samples that match the regexp rpt.options.symbol.
  32. // The sources are sorted by function name and then by filename to
  33. // eliminate potential nondeterminism.
  34. func printSource(w io.Writer, rpt *Report) error {
  35. o := rpt.options
  36. g := rpt.newGraph(nil)
  37. // Identify all the functions that match the regexp provided.
  38. // Group nodes for each matching function.
  39. var functions graph.Nodes
  40. functionNodes := make(map[string]graph.Nodes)
  41. for _, n := range g.Nodes {
  42. if !o.Symbol.MatchString(n.Info.Name) {
  43. continue
  44. }
  45. if functionNodes[n.Info.Name] == nil {
  46. functions = append(functions, n)
  47. }
  48. functionNodes[n.Info.Name] = append(functionNodes[n.Info.Name], n)
  49. }
  50. functions.Sort(graph.NameOrder)
  51. fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
  52. for _, fn := range functions {
  53. name := fn.Info.Name
  54. // Identify all the source files associated to this function.
  55. // Group nodes for each source file.
  56. var sourceFiles graph.Nodes
  57. fileNodes := make(map[string]graph.Nodes)
  58. for _, n := range functionNodes[name] {
  59. if n.Info.File == "" {
  60. continue
  61. }
  62. if fileNodes[n.Info.File] == nil {
  63. sourceFiles = append(sourceFiles, n)
  64. }
  65. fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
  66. }
  67. if len(sourceFiles) == 0 {
  68. fmt.Fprintf(w, "No source information for %s\n", name)
  69. continue
  70. }
  71. sourceFiles.Sort(graph.FileOrder)
  72. // Print each file associated with this function.
  73. for _, fl := range sourceFiles {
  74. filename := fl.Info.File
  75. fns := fileNodes[filename]
  76. flatSum, cumSum := fns.Sum()
  77. fnodes, path, err := getFunctionSource(name, filename, fns, 0, 0)
  78. fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, path)
  79. fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
  80. rpt.formatValue(flatSum), rpt.formatValue(cumSum),
  81. percentage(cumSum, rpt.total))
  82. if err != nil {
  83. fmt.Fprintf(w, " Error: %v\n", err)
  84. continue
  85. }
  86. for _, fn := range fnodes {
  87. fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)
  88. }
  89. }
  90. }
  91. return nil
  92. }
  93. // printWebSource prints an annotated source listing, include all
  94. // functions with samples that match the regexp rpt.options.symbol.
  95. func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
  96. o := rpt.options
  97. g := rpt.newGraph(nil)
  98. // If the regexp source can be parsed as an address, also match
  99. // functions that land on that address.
  100. var address *uint64
  101. if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
  102. address = &hex
  103. }
  104. // Extract interesting symbols from binary files in the profile and
  105. // classify samples per symbol.
  106. symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj)
  107. symNodes := nodesPerSymbol(g.Nodes, symbols)
  108. // Sort symbols for printing.
  109. var syms objSymbols
  110. for s := range symNodes {
  111. syms = append(syms, s)
  112. }
  113. sort.Sort(syms)
  114. if len(syms) == 0 {
  115. return fmt.Errorf("no samples found on routines matching: %s", o.Symbol.String())
  116. }
  117. printHeader(w, rpt)
  118. for _, s := range syms {
  119. name := s.sym.Name[0]
  120. // Identify sources associated to a symbol by examining
  121. // symbol samples. Classify samples per source file.
  122. var sourceFiles graph.Nodes
  123. fileNodes := make(map[string]graph.Nodes)
  124. for _, n := range symNodes[s] {
  125. if n.Info.File == "" {
  126. continue
  127. }
  128. if fileNodes[n.Info.File] == nil {
  129. sourceFiles = append(sourceFiles, n)
  130. }
  131. fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
  132. }
  133. if len(sourceFiles) == 0 {
  134. fmt.Fprintf(w, "No source information for %s\n", name)
  135. continue
  136. }
  137. sourceFiles.Sort(graph.FileOrder)
  138. // Print each file associated with this function.
  139. for _, fl := range sourceFiles {
  140. filename := fl.Info.File
  141. fns := fileNodes[filename]
  142. asm := assemblyPerSourceLine(symbols, fns, filename, obj)
  143. start, end := sourceCoordinates(asm)
  144. fnodes, path, err := getFunctionSource(name, filename, fns, start, end)
  145. if err != nil {
  146. fnodes, path = getMissingFunctionSource(filename, asm, start, end)
  147. }
  148. flatSum, cumSum := fnodes.Sum()
  149. printFunctionHeader(w, name, path, flatSum, cumSum, rpt)
  150. for _, fn := range fnodes {
  151. printFunctionSourceLine(w, fn, asm[fn.Info.Lineno], rpt)
  152. }
  153. printFunctionClosing(w)
  154. }
  155. }
  156. printPageClosing(w)
  157. return nil
  158. }
  159. // sourceCoordinates returns the lowest and highest line numbers from
  160. // a set of assembly statements.
  161. func sourceCoordinates(asm map[int]graph.Nodes) (start, end int) {
  162. for l := range asm {
  163. if start == 0 || l < start {
  164. start = l
  165. }
  166. if end == 0 || l > end {
  167. end = l
  168. }
  169. }
  170. return start, end
  171. }
  172. // assemblyPerSourceLine disassembles the binary containing a symbol
  173. // and classifies the assembly instructions according to its
  174. // corresponding source line, annotating them with a set of samples.
  175. func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj plugin.ObjTool) map[int]graph.Nodes {
  176. assembly := make(map[int]graph.Nodes)
  177. // Identify symbol to use for this collection of samples.
  178. o := findMatchingSymbol(objSyms, rs)
  179. if o == nil {
  180. return assembly
  181. }
  182. // Extract assembly for matched symbol
  183. insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End)
  184. if err != nil {
  185. return assembly
  186. }
  187. srcBase := filepath.Base(src)
  188. anodes := annotateAssembly(insns, rs, o.base)
  189. var lineno = 0
  190. for _, an := range anodes {
  191. if filepath.Base(an.Info.File) == srcBase {
  192. lineno = an.Info.Lineno
  193. }
  194. if lineno != 0 {
  195. assembly[lineno] = append(assembly[lineno], an)
  196. }
  197. }
  198. return assembly
  199. }
  200. // findMatchingSymbol looks for the symbol that corresponds to a set
  201. // of samples, by comparing their addresses.
  202. func findMatchingSymbol(objSyms []*objSymbol, ns graph.Nodes) *objSymbol {
  203. for _, n := range ns {
  204. for _, o := range objSyms {
  205. if filepath.Base(o.sym.File) == n.Info.Objfile &&
  206. o.sym.Start <= n.Info.Address-o.base &&
  207. n.Info.Address-o.base <= o.sym.End {
  208. return o
  209. }
  210. }
  211. }
  212. return nil
  213. }
  214. // printHeader prints the page header for a weblist report.
  215. func printHeader(w io.Writer, rpt *Report) {
  216. fmt.Fprintln(w, weblistPageHeader)
  217. var labels []string
  218. for _, l := range ProfileLabels(rpt) {
  219. labels = append(labels, template.HTMLEscapeString(l))
  220. }
  221. fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
  222. strings.Join(labels, "<br>\n"),
  223. rpt.formatValue(rpt.total),
  224. )
  225. }
  226. // printFunctionHeader prints a function header for a weblist report.
  227. func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
  228. fmt.Fprintf(w, `<h1>%s</h1>%s
  229. <pre onClick="pprof_toggle_asm()">
  230. Total: %10s %10s (flat, cum) %s
  231. `,
  232. template.HTMLEscapeString(name), template.HTMLEscapeString(path),
  233. rpt.formatValue(flatSum), rpt.formatValue(cumSum),
  234. percentage(cumSum, rpt.total))
  235. }
  236. // printFunctionSourceLine prints a source line and the corresponding assembly.
  237. func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly graph.Nodes, rpt *Report) {
  238. if len(assembly) == 0 {
  239. fmt.Fprintf(w,
  240. "<span class=line> %6d</span> <span class=nop> %10s %10s %s </span>\n",
  241. fn.Info.Lineno,
  242. valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
  243. template.HTMLEscapeString(fn.Info.Name))
  244. return
  245. }
  246. fmt.Fprintf(w,
  247. "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %s </span>",
  248. fn.Info.Lineno,
  249. valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
  250. template.HTMLEscapeString(fn.Info.Name))
  251. fmt.Fprint(w, "<span class=asm>")
  252. for _, an := range assembly {
  253. var fileline string
  254. class := "disasmloc"
  255. if an.Info.File != "" {
  256. fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.Info.File), an.Info.Lineno)
  257. if an.Info.Lineno != fn.Info.Lineno {
  258. class = "unimportant"
  259. }
  260. }
  261. fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "",
  262. valueOrDot(an.Flat, rpt), valueOrDot(an.Cum, rpt),
  263. an.Info.Address,
  264. template.HTMLEscapeString(an.Info.Name),
  265. class,
  266. template.HTMLEscapeString(fileline))
  267. }
  268. fmt.Fprintln(w, "</span>")
  269. }
  270. // printFunctionClosing prints the end of a function in a weblist report.
  271. func printFunctionClosing(w io.Writer) {
  272. fmt.Fprintln(w, "</pre>")
  273. }
  274. // printPageClosing prints the end of the page in a weblist report.
  275. func printPageClosing(w io.Writer) {
  276. fmt.Fprintln(w, weblistPageClosing)
  277. }
  278. // getFunctionSource collects the sources of a function from a source
  279. // file and annotates it with the samples in fns. Returns the sources
  280. // as nodes, using the info.name field to hold the source code.
  281. func getFunctionSource(fun, file string, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
  282. f, file, err := adjustSourcePath(file)
  283. if err != nil {
  284. return nil, file, err
  285. }
  286. lineNodes := make(map[int]graph.Nodes)
  287. // Collect source coordinates from profile.
  288. const margin = 5 // Lines before first/after last sample.
  289. if start == 0 {
  290. if fns[0].Info.StartLine != 0 {
  291. start = fns[0].Info.StartLine
  292. } else {
  293. start = fns[0].Info.Lineno - margin
  294. }
  295. } else {
  296. start -= margin
  297. }
  298. if end == 0 {
  299. end = fns[0].Info.Lineno
  300. }
  301. end += margin
  302. for _, n := range fns {
  303. lineno := n.Info.Lineno
  304. nodeStart := n.Info.StartLine
  305. if nodeStart == 0 {
  306. nodeStart = lineno - margin
  307. }
  308. nodeEnd := lineno + margin
  309. if nodeStart < start {
  310. start = nodeStart
  311. } else if nodeEnd > end {
  312. end = nodeEnd
  313. }
  314. lineNodes[lineno] = append(lineNodes[lineno], n)
  315. }
  316. var src graph.Nodes
  317. buf := bufio.NewReader(f)
  318. lineno := 1
  319. for {
  320. line, err := buf.ReadString('\n')
  321. if err != nil {
  322. if err != io.EOF {
  323. return nil, file, err
  324. }
  325. if line == "" {
  326. break
  327. }
  328. }
  329. if lineno >= start {
  330. flat, cum := lineNodes[lineno].Sum()
  331. src = append(src, &graph.Node{
  332. Info: graph.NodeInfo{
  333. Name: strings.TrimRight(line, "\n"),
  334. Lineno: lineno,
  335. },
  336. Flat: flat,
  337. Cum: cum,
  338. })
  339. }
  340. lineno++
  341. if lineno > end {
  342. break
  343. }
  344. }
  345. return src, file, nil
  346. }
  347. // getMissingFunctionSource creates a dummy function body to point to
  348. // the source file and annotates it with the samples in asm.
  349. func getMissingFunctionSource(filename string, asm map[int]graph.Nodes, start, end int) (graph.Nodes, string) {
  350. var fnodes graph.Nodes
  351. for i := start; i <= end; i++ {
  352. lrs := asm[i]
  353. if len(lrs) == 0 {
  354. continue
  355. }
  356. flat, cum := lrs.Sum()
  357. fnodes = append(fnodes, &graph.Node{
  358. Info: graph.NodeInfo{
  359. Name: "???",
  360. Lineno: i,
  361. },
  362. Flat: flat,
  363. Cum: cum,
  364. })
  365. }
  366. return fnodes, filename
  367. }
  368. // adjustSourcePath adjusts the path for a source file by trimmming
  369. // known prefixes and searching for the file on all parents of the
  370. // current working dir.
  371. func adjustSourcePath(path string) (*os.File, string, error) {
  372. path = trimPath(path)
  373. f, err := os.Open(path)
  374. if err == nil {
  375. return f, path, nil
  376. }
  377. if dir, wderr := os.Getwd(); wderr == nil {
  378. for {
  379. parent := filepath.Dir(dir)
  380. if parent == dir {
  381. break
  382. }
  383. if f, err := os.Open(filepath.Join(parent, path)); err == nil {
  384. return f, filepath.Join(parent, path), nil
  385. }
  386. dir = parent
  387. }
  388. }
  389. return nil, path, err
  390. }
  391. // trimPath cleans up a path by removing prefixes that are commonly
  392. // found on profiles.
  393. func trimPath(path string) string {
  394. basePaths := []string{
  395. "/proc/self/cwd/./",
  396. "/proc/self/cwd/",
  397. }
  398. sPath := filepath.ToSlash(path)
  399. for _, base := range basePaths {
  400. if strings.HasPrefix(sPath, base) {
  401. return filepath.FromSlash(sPath[len(base):])
  402. }
  403. }
  404. return path
  405. }