暂无描述

source.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  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. fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
  59. for _, fn := range functions {
  60. name := fn.Info.Name
  61. // Identify all the source files associated to this function.
  62. // Group nodes for each source file.
  63. var sourceFiles graph.Nodes
  64. fileNodes := make(map[string]graph.Nodes)
  65. for _, n := range functionNodes[name] {
  66. if n.Info.File == "" {
  67. continue
  68. }
  69. if fileNodes[n.Info.File] == nil {
  70. sourceFiles = append(sourceFiles, n)
  71. }
  72. fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
  73. }
  74. if len(sourceFiles) == 0 {
  75. fmt.Fprintf(w, "No source information for %s\n", name)
  76. continue
  77. }
  78. sourceFiles.Sort(graph.FileOrder)
  79. // Print each file associated with this function.
  80. for _, fl := range sourceFiles {
  81. filename := fl.Info.File
  82. fns := fileNodes[filename]
  83. flatSum, cumSum := fns.Sum()
  84. fnodes, _, err := getSourceFromFile(filename, sourcePath, fns, 0, 0)
  85. fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
  86. fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
  87. rpt.formatValue(flatSum), rpt.formatValue(cumSum),
  88. percentage(cumSum, rpt.total))
  89. if err != nil {
  90. fmt.Fprintf(w, " Error: %v\n", err)
  91. continue
  92. }
  93. for _, fn := range fnodes {
  94. fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)
  95. }
  96. }
  97. }
  98. return nil
  99. }
  100. // printWebSource prints an annotated source listing, include all
  101. // functions with samples that match the regexp rpt.options.symbol.
  102. func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
  103. o := rpt.options
  104. g := rpt.newGraph(nil)
  105. // If the regexp source can be parsed as an address, also match
  106. // functions that land on that address.
  107. var address *uint64
  108. if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
  109. address = &hex
  110. }
  111. sourcePath := o.SourcePath
  112. if sourcePath == "" {
  113. wd, err := os.Getwd()
  114. if err != nil {
  115. return fmt.Errorf("Could not stat current dir: %v", err)
  116. }
  117. sourcePath = wd
  118. }
  119. type fileFunction struct {
  120. fileName, functionName string
  121. }
  122. // Extract interesting symbols from binary files in the profile and
  123. // classify samples per symbol.
  124. symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj)
  125. symNodes := nodesPerSymbol(g.Nodes, symbols)
  126. // Identify sources associated to a symbol by examining
  127. // symbol samples. Classify samples per source file.
  128. fileNodes := make(map[fileFunction]graph.Nodes)
  129. if len(symNodes) == 0 {
  130. for _, n := range g.Nodes {
  131. if n.Info.File == "" || !o.Symbol.MatchString(n.Info.Name) {
  132. continue
  133. }
  134. ff := fileFunction{n.Info.File, n.Info.Name}
  135. fileNodes[ff] = append(fileNodes[ff], n)
  136. }
  137. } else {
  138. for _, nodes := range symNodes {
  139. for _, n := range nodes {
  140. if n.Info.File != "" {
  141. ff := fileFunction{n.Info.File, n.Info.Name}
  142. fileNodes[ff] = append(fileNodes[ff], n)
  143. }
  144. }
  145. }
  146. }
  147. if len(fileNodes) == 0 {
  148. return fmt.Errorf("No source information for %s", o.Symbol.String())
  149. }
  150. sourceFiles := make(graph.Nodes, 0, len(fileNodes))
  151. for _, nodes := range fileNodes {
  152. sNode := *nodes[0]
  153. sNode.Flat, sNode.Cum = nodes.Sum()
  154. sourceFiles = append(sourceFiles, &sNode)
  155. }
  156. sourceFiles.Sort(graph.FileOrder)
  157. // Print each file associated with this function.
  158. printHeader(w, rpt)
  159. for _, n := range sourceFiles {
  160. ff := fileFunction{n.Info.File, n.Info.Name}
  161. fns := fileNodes[ff]
  162. asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj)
  163. start, end := sourceCoordinates(asm)
  164. fnodes, path, err := getSourceFromFile(ff.fileName, sourcePath, fns, start, end)
  165. if err != nil {
  166. fnodes, path = getMissingFunctionSource(ff.fileName, asm, start, end)
  167. }
  168. printFunctionHeader(w, ff.functionName, path, n.Flat, n.Cum, rpt)
  169. for _, fn := range fnodes {
  170. printFunctionSourceLine(w, fn, asm[fn.Info.Lineno], rpt)
  171. }
  172. printFunctionClosing(w)
  173. }
  174. printPageClosing(w)
  175. return nil
  176. }
  177. // sourceCoordinates returns the lowest and highest line numbers from
  178. // a set of assembly statements.
  179. func sourceCoordinates(asm map[int][]assemblyInstruction) (start, end int) {
  180. for l := range asm {
  181. if start == 0 || l < start {
  182. start = l
  183. }
  184. if end == 0 || l > end {
  185. end = l
  186. }
  187. }
  188. return start, end
  189. }
  190. // assemblyPerSourceLine disassembles the binary containing a symbol
  191. // and classifies the assembly instructions according to its
  192. // corresponding source line, annotating them with a set of samples.
  193. func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj plugin.ObjTool) map[int][]assemblyInstruction {
  194. assembly := make(map[int][]assemblyInstruction)
  195. // Identify symbol to use for this collection of samples.
  196. o := findMatchingSymbol(objSyms, rs)
  197. if o == nil {
  198. return assembly
  199. }
  200. // Extract assembly for matched symbol
  201. insts, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End)
  202. if err != nil {
  203. return assembly
  204. }
  205. srcBase := filepath.Base(src)
  206. anodes := annotateAssembly(insts, rs, o.base)
  207. var lineno = 0
  208. var prevline = 0
  209. for _, an := range anodes {
  210. if filepath.Base(an.file) == srcBase {
  211. lineno = an.line
  212. }
  213. if lineno != 0 {
  214. if lineno != prevline {
  215. // This instruction starts a new block
  216. // of contiguous instructions on this line.
  217. an.startsBlock = true
  218. }
  219. prevline = lineno
  220. assembly[lineno] = append(assembly[lineno], an)
  221. }
  222. }
  223. return assembly
  224. }
  225. // findMatchingSymbol looks for the symbol that corresponds to a set
  226. // of samples, by comparing their addresses.
  227. func findMatchingSymbol(objSyms []*objSymbol, ns graph.Nodes) *objSymbol {
  228. for _, n := range ns {
  229. for _, o := range objSyms {
  230. if filepath.Base(o.sym.File) == filepath.Base(n.Info.Objfile) &&
  231. o.sym.Start <= n.Info.Address-o.base &&
  232. n.Info.Address-o.base <= o.sym.End {
  233. return o
  234. }
  235. }
  236. }
  237. return nil
  238. }
  239. // printHeader prints the page header for a weblist report.
  240. func printHeader(w io.Writer, rpt *Report) {
  241. fmt.Fprintln(w, weblistPageHeader)
  242. var labels []string
  243. for _, l := range ProfileLabels(rpt) {
  244. labels = append(labels, template.HTMLEscapeString(l))
  245. }
  246. fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
  247. strings.Join(labels, "<br>\n"),
  248. rpt.formatValue(rpt.total),
  249. )
  250. }
  251. // printFunctionHeader prints a function header for a weblist report.
  252. func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
  253. fmt.Fprintf(w, `<h1>%s</h1>%s
  254. <pre onClick="pprof_toggle_asm(event)">
  255. Total: %10s %10s (flat, cum) %s
  256. `,
  257. template.HTMLEscapeString(name), template.HTMLEscapeString(path),
  258. rpt.formatValue(flatSum), rpt.formatValue(cumSum),
  259. percentage(cumSum, rpt.total))
  260. }
  261. // printFunctionSourceLine prints a source line and the corresponding assembly.
  262. func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyInstruction, rpt *Report) {
  263. if len(assembly) == 0 {
  264. fmt.Fprintf(w,
  265. "<span class=line> %6d</span> <span class=nop> %10s %10s %s </span>\n",
  266. fn.Info.Lineno,
  267. valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
  268. template.HTMLEscapeString(fn.Info.Name))
  269. return
  270. }
  271. fmt.Fprintf(w,
  272. "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %s </span>",
  273. fn.Info.Lineno,
  274. valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
  275. template.HTMLEscapeString(fn.Info.Name))
  276. fmt.Fprint(w, "<span class=asm>")
  277. for i, an := range assembly {
  278. if an.startsBlock && i != 0 {
  279. // Insert a separator between discontiguous blocks.
  280. fmt.Fprintf(w, " %8s %30s\n", "", "⋮")
  281. }
  282. var fileline string
  283. class := "disasmloc"
  284. if an.file != "" {
  285. fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.file), an.line)
  286. if an.line != fn.Info.Lineno {
  287. class = "unimportant"
  288. }
  289. }
  290. flat, cum := an.flat, an.cum
  291. if an.flatDiv != 0 {
  292. flat = flat / an.flatDiv
  293. }
  294. if an.cumDiv != 0 {
  295. cum = cum / an.cumDiv
  296. }
  297. fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=%s>%s</span>\n", "",
  298. valueOrDot(flat, rpt), valueOrDot(cum, rpt),
  299. an.address,
  300. template.HTMLEscapeString(fmt.Sprintf("%-48s", strings.Replace(an.instruction, "\t", " ", -1))),
  301. class,
  302. template.HTMLEscapeString(fileline))
  303. }
  304. fmt.Fprintln(w, "</span>")
  305. }
  306. // printFunctionClosing prints the end of a function in a weblist report.
  307. func printFunctionClosing(w io.Writer) {
  308. fmt.Fprintln(w, "</pre>")
  309. }
  310. // printPageClosing prints the end of the page in a weblist report.
  311. func printPageClosing(w io.Writer) {
  312. fmt.Fprintln(w, weblistPageClosing)
  313. }
  314. // getSourceFromFile collects the sources of a function from a source
  315. // file and annotates it with the samples in fns. Returns the sources
  316. // as nodes, using the info.name field to hold the source code.
  317. func getSourceFromFile(file, sourcePath string, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
  318. file = trimPath(file)
  319. f, err := openSourceFile(file, sourcePath)
  320. if err != nil {
  321. return nil, file, err
  322. }
  323. lineNodes := make(map[int]graph.Nodes)
  324. // Collect source coordinates from profile.
  325. const margin = 5 // Lines before first/after last sample.
  326. if start == 0 {
  327. if fns[0].Info.StartLine != 0 {
  328. start = fns[0].Info.StartLine
  329. } else {
  330. start = fns[0].Info.Lineno - margin
  331. }
  332. } else {
  333. start -= margin
  334. }
  335. if end == 0 {
  336. end = fns[0].Info.Lineno
  337. }
  338. end += margin
  339. for _, n := range fns {
  340. lineno := n.Info.Lineno
  341. nodeStart := n.Info.StartLine
  342. if nodeStart == 0 {
  343. nodeStart = lineno - margin
  344. }
  345. nodeEnd := lineno + margin
  346. if nodeStart < start {
  347. start = nodeStart
  348. } else if nodeEnd > end {
  349. end = nodeEnd
  350. }
  351. lineNodes[lineno] = append(lineNodes[lineno], n)
  352. }
  353. var src graph.Nodes
  354. buf := bufio.NewReader(f)
  355. lineno := 1
  356. for {
  357. line, err := buf.ReadString('\n')
  358. if err != nil {
  359. if err != io.EOF {
  360. return nil, file, err
  361. }
  362. if line == "" {
  363. break
  364. }
  365. }
  366. if lineno >= start {
  367. flat, cum := lineNodes[lineno].Sum()
  368. src = append(src, &graph.Node{
  369. Info: graph.NodeInfo{
  370. Name: strings.TrimRight(line, "\n"),
  371. Lineno: lineno,
  372. },
  373. Flat: flat,
  374. Cum: cum,
  375. })
  376. }
  377. lineno++
  378. if lineno > end {
  379. break
  380. }
  381. }
  382. return src, file, nil
  383. }
  384. // getMissingFunctionSource creates a dummy function body to point to
  385. // the source file and annotates it with the samples in asm.
  386. func getMissingFunctionSource(filename string, asm map[int][]assemblyInstruction, start, end int) (graph.Nodes, string) {
  387. var fnodes graph.Nodes
  388. for i := start; i <= end; i++ {
  389. insts := asm[i]
  390. if len(insts) == 0 {
  391. continue
  392. }
  393. var group assemblyInstruction
  394. for _, insn := range insts {
  395. group.flat += insn.flat
  396. group.cum += insn.cum
  397. group.flatDiv += insn.flatDiv
  398. group.cumDiv += insn.cumDiv
  399. }
  400. flat := group.flatValue()
  401. cum := group.cumValue()
  402. fnodes = append(fnodes, &graph.Node{
  403. Info: graph.NodeInfo{
  404. Name: "???",
  405. Lineno: i,
  406. },
  407. Flat: flat,
  408. Cum: cum,
  409. })
  410. }
  411. return fnodes, filename
  412. }
  413. // openSourceFile opens a source file from a name encoded in a
  414. // profile. File names in a profile after often relative paths, so
  415. // search them in each of the paths in searchPath (or CWD by default),
  416. // and their parents.
  417. func openSourceFile(path, searchPath string) (*os.File, error) {
  418. if filepath.IsAbs(path) {
  419. f, err := os.Open(path)
  420. return f, err
  421. }
  422. // Scan each component of the path
  423. for _, dir := range strings.Split(searchPath, ":") {
  424. // Search up for every parent of each possible path.
  425. for {
  426. filename := filepath.Join(dir, path)
  427. if f, err := os.Open(filename); err == nil {
  428. return f, nil
  429. }
  430. parent := filepath.Dir(dir)
  431. if parent == dir {
  432. break
  433. }
  434. dir = parent
  435. }
  436. }
  437. return nil, fmt.Errorf("Could not find file %s on path %s", path, searchPath)
  438. }
  439. // trimPath cleans up a path by removing prefixes that are commonly
  440. // found on profiles.
  441. func trimPath(path string) string {
  442. basePaths := []string{
  443. "/proc/self/cwd/./",
  444. "/proc/self/cwd/",
  445. }
  446. sPath := filepath.ToSlash(path)
  447. for _, base := range basePaths {
  448. if strings.HasPrefix(sPath, base) {
  449. return filepath.FromSlash(sPath[len(base):])
  450. }
  451. }
  452. return path
  453. }