Без опису

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  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\n", 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. for _, an := range anodes {
  209. if filepath.Base(an.file) == srcBase {
  210. lineno = an.line
  211. }
  212. if lineno != 0 {
  213. assembly[lineno] = append(assembly[lineno], an)
  214. }
  215. }
  216. return assembly
  217. }
  218. // findMatchingSymbol looks for the symbol that corresponds to a set
  219. // of samples, by comparing their addresses.
  220. func findMatchingSymbol(objSyms []*objSymbol, ns graph.Nodes) *objSymbol {
  221. for _, n := range ns {
  222. for _, o := range objSyms {
  223. if filepath.Base(o.sym.File) == filepath.Base(n.Info.Objfile) &&
  224. o.sym.Start <= n.Info.Address-o.base &&
  225. n.Info.Address-o.base <= o.sym.End {
  226. return o
  227. }
  228. }
  229. }
  230. return nil
  231. }
  232. // printHeader prints the page header for a weblist report.
  233. func printHeader(w io.Writer, rpt *Report) {
  234. fmt.Fprintln(w, weblistPageHeader)
  235. var labels []string
  236. for _, l := range ProfileLabels(rpt) {
  237. labels = append(labels, template.HTMLEscapeString(l))
  238. }
  239. fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
  240. strings.Join(labels, "<br>\n"),
  241. rpt.formatValue(rpt.total),
  242. )
  243. }
  244. // printFunctionHeader prints a function header for a weblist report.
  245. func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
  246. fmt.Fprintf(w, `<h1>%s</h1>%s
  247. <pre onClick="pprof_toggle_asm(event)">
  248. Total: %10s %10s (flat, cum) %s
  249. `,
  250. template.HTMLEscapeString(name), template.HTMLEscapeString(path),
  251. rpt.formatValue(flatSum), rpt.formatValue(cumSum),
  252. percentage(cumSum, rpt.total))
  253. }
  254. // printFunctionSourceLine prints a source line and the corresponding assembly.
  255. func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyInstruction, rpt *Report) {
  256. if len(assembly) == 0 {
  257. fmt.Fprintf(w,
  258. "<span class=line> %6d</span> <span class=nop> %10s %10s %s </span>\n",
  259. fn.Info.Lineno,
  260. valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
  261. template.HTMLEscapeString(fn.Info.Name))
  262. return
  263. }
  264. fmt.Fprintf(w,
  265. "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %s </span>",
  266. fn.Info.Lineno,
  267. valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
  268. template.HTMLEscapeString(fn.Info.Name))
  269. fmt.Fprint(w, "<span class=asm>")
  270. for _, an := range assembly {
  271. var fileline string
  272. class := "disasmloc"
  273. if an.file != "" {
  274. fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.file), an.line)
  275. if an.line != fn.Info.Lineno {
  276. class = "unimportant"
  277. }
  278. }
  279. flat, cum := an.flat, an.cum
  280. if an.flatDiv != 0 {
  281. flat = flat / an.flatDiv
  282. }
  283. if an.cumDiv != 0 {
  284. cum = cum / an.cumDiv
  285. }
  286. fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "",
  287. valueOrDot(flat, rpt), valueOrDot(cum, rpt),
  288. an.address,
  289. template.HTMLEscapeString(an.instruction),
  290. class,
  291. template.HTMLEscapeString(fileline))
  292. }
  293. fmt.Fprintln(w, "</span>")
  294. }
  295. // printFunctionClosing prints the end of a function in a weblist report.
  296. func printFunctionClosing(w io.Writer) {
  297. fmt.Fprintln(w, "</pre>")
  298. }
  299. // printPageClosing prints the end of the page in a weblist report.
  300. func printPageClosing(w io.Writer) {
  301. fmt.Fprintln(w, weblistPageClosing)
  302. }
  303. // getSourceFromFile collects the sources of a function from a source
  304. // file and annotates it with the samples in fns. Returns the sources
  305. // as nodes, using the info.name field to hold the source code.
  306. func getSourceFromFile(file, sourcePath string, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
  307. file = trimPath(file)
  308. f, err := openSourceFile(file, sourcePath)
  309. if err != nil {
  310. return nil, file, err
  311. }
  312. lineNodes := make(map[int]graph.Nodes)
  313. // Collect source coordinates from profile.
  314. const margin = 5 // Lines before first/after last sample.
  315. if start == 0 {
  316. if fns[0].Info.StartLine != 0 {
  317. start = fns[0].Info.StartLine
  318. } else {
  319. start = fns[0].Info.Lineno - margin
  320. }
  321. } else {
  322. start -= margin
  323. }
  324. if end == 0 {
  325. end = fns[0].Info.Lineno
  326. }
  327. end += margin
  328. for _, n := range fns {
  329. lineno := n.Info.Lineno
  330. nodeStart := n.Info.StartLine
  331. if nodeStart == 0 {
  332. nodeStart = lineno - margin
  333. }
  334. nodeEnd := lineno + margin
  335. if nodeStart < start {
  336. start = nodeStart
  337. } else if nodeEnd > end {
  338. end = nodeEnd
  339. }
  340. lineNodes[lineno] = append(lineNodes[lineno], n)
  341. }
  342. var src graph.Nodes
  343. buf := bufio.NewReader(f)
  344. lineno := 1
  345. for {
  346. line, err := buf.ReadString('\n')
  347. if err != nil {
  348. if err != io.EOF {
  349. return nil, file, err
  350. }
  351. if line == "" {
  352. break
  353. }
  354. }
  355. if lineno >= start {
  356. flat, cum := lineNodes[lineno].Sum()
  357. src = append(src, &graph.Node{
  358. Info: graph.NodeInfo{
  359. Name: strings.TrimRight(line, "\n"),
  360. Lineno: lineno,
  361. },
  362. Flat: flat,
  363. Cum: cum,
  364. })
  365. }
  366. lineno++
  367. if lineno > end {
  368. break
  369. }
  370. }
  371. return src, file, nil
  372. }
  373. // getMissingFunctionSource creates a dummy function body to point to
  374. // the source file and annotates it with the samples in asm.
  375. func getMissingFunctionSource(filename string, asm map[int][]assemblyInstruction, start, end int) (graph.Nodes, string) {
  376. var fnodes graph.Nodes
  377. for i := start; i <= end; i++ {
  378. insts := asm[i]
  379. if len(insts) == 0 {
  380. continue
  381. }
  382. var group assemblyInstruction
  383. for _, insn := range insts {
  384. group.flat += insn.flat
  385. group.cum += insn.cum
  386. group.flatDiv += insn.flatDiv
  387. group.cumDiv += insn.cumDiv
  388. }
  389. flat := group.flatValue()
  390. cum := group.cumValue()
  391. fnodes = append(fnodes, &graph.Node{
  392. Info: graph.NodeInfo{
  393. Name: "???",
  394. Lineno: i,
  395. },
  396. Flat: flat,
  397. Cum: cum,
  398. })
  399. }
  400. return fnodes, filename
  401. }
  402. // openSourceFile opens a source file from a name encoded in a
  403. // profile. File names in a profile after often relative paths, so
  404. // search them in each of the paths in searchPath (or CWD by default),
  405. // and their parents.
  406. func openSourceFile(path, searchPath string) (*os.File, error) {
  407. if filepath.IsAbs(path) {
  408. f, err := os.Open(path)
  409. return f, err
  410. }
  411. // Scan each component of the path
  412. for _, dir := range strings.Split(searchPath, ":") {
  413. // Search up for every parent of each possible path.
  414. for {
  415. filename := filepath.Join(dir, path)
  416. if f, err := os.Open(filename); err == nil {
  417. return f, nil
  418. }
  419. parent := filepath.Dir(dir)
  420. if parent == dir {
  421. break
  422. }
  423. dir = parent
  424. }
  425. }
  426. return nil, fmt.Errorf("Could not find file %s on path %s", path, searchPath)
  427. }
  428. // trimPath cleans up a path by removing prefixes that are commonly
  429. // found on profiles.
  430. func trimPath(path string) string {
  431. basePaths := []string{
  432. "/proc/self/cwd/./",
  433. "/proc/self/cwd/",
  434. }
  435. sPath := filepath.ToSlash(path)
  436. for _, base := range basePaths {
  437. if strings.HasPrefix(sPath, base) {
  438. return filepath.FromSlash(sPath[len(base):])
  439. }
  440. }
  441. return path
  442. }