Без опису

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