Няма описание

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