Açıklama Yok

source.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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]graph.Nodes) (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]graph.Nodes {
  194. assembly := make(map[int]graph.Nodes)
  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. insns, 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(insns, rs, o.base)
  207. var lineno = 0
  208. for _, an := range anodes {
  209. if filepath.Base(an.Info.File) == srcBase {
  210. lineno = an.Info.Lineno
  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()">
  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 graph.Nodes, 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.Info.File != "" {
  274. fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.Info.File), an.Info.Lineno)
  275. if an.Info.Lineno != fn.Info.Lineno {
  276. class = "unimportant"
  277. }
  278. }
  279. fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "",
  280. valueOrDot(an.Flat, rpt), valueOrDot(an.Cum, rpt),
  281. an.Info.Address,
  282. template.HTMLEscapeString(an.Info.Name),
  283. class,
  284. template.HTMLEscapeString(fileline))
  285. }
  286. fmt.Fprintln(w, "</span>")
  287. }
  288. // printFunctionClosing prints the end of a function in a weblist report.
  289. func printFunctionClosing(w io.Writer) {
  290. fmt.Fprintln(w, "</pre>")
  291. }
  292. // printPageClosing prints the end of the page in a weblist report.
  293. func printPageClosing(w io.Writer) {
  294. fmt.Fprintln(w, weblistPageClosing)
  295. }
  296. // getSourceFromFile collects the sources of a function from a source
  297. // file and annotates it with the samples in fns. Returns the sources
  298. // as nodes, using the info.name field to hold the source code.
  299. func getSourceFromFile(file, sourcePath string, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
  300. file = trimPath(file)
  301. f, err := openSourceFile(file, sourcePath)
  302. if err != nil {
  303. return nil, file, err
  304. }
  305. lineNodes := make(map[int]graph.Nodes)
  306. // Collect source coordinates from profile.
  307. const margin = 5 // Lines before first/after last sample.
  308. if start == 0 {
  309. if fns[0].Info.StartLine != 0 {
  310. start = fns[0].Info.StartLine
  311. } else {
  312. start = fns[0].Info.Lineno - margin
  313. }
  314. } else {
  315. start -= margin
  316. }
  317. if end == 0 {
  318. end = fns[0].Info.Lineno
  319. }
  320. end += margin
  321. for _, n := range fns {
  322. lineno := n.Info.Lineno
  323. nodeStart := n.Info.StartLine
  324. if nodeStart == 0 {
  325. nodeStart = lineno - margin
  326. }
  327. nodeEnd := lineno + margin
  328. if nodeStart < start {
  329. start = nodeStart
  330. } else if nodeEnd > end {
  331. end = nodeEnd
  332. }
  333. lineNodes[lineno] = append(lineNodes[lineno], n)
  334. }
  335. var src graph.Nodes
  336. buf := bufio.NewReader(f)
  337. lineno := 1
  338. for {
  339. line, err := buf.ReadString('\n')
  340. if err != nil {
  341. if err != io.EOF {
  342. return nil, file, err
  343. }
  344. if line == "" {
  345. break
  346. }
  347. }
  348. if lineno >= start {
  349. flat, cum := lineNodes[lineno].Sum()
  350. src = append(src, &graph.Node{
  351. Info: graph.NodeInfo{
  352. Name: strings.TrimRight(line, "\n"),
  353. Lineno: lineno,
  354. },
  355. Flat: flat,
  356. Cum: cum,
  357. })
  358. }
  359. lineno++
  360. if lineno > end {
  361. break
  362. }
  363. }
  364. return src, file, nil
  365. }
  366. // getMissingFunctionSource creates a dummy function body to point to
  367. // the source file and annotates it with the samples in asm.
  368. func getMissingFunctionSource(filename string, asm map[int]graph.Nodes, start, end int) (graph.Nodes, string) {
  369. var fnodes graph.Nodes
  370. for i := start; i <= end; i++ {
  371. lrs := asm[i]
  372. if len(lrs) == 0 {
  373. continue
  374. }
  375. flat, cum := lrs.Sum()
  376. fnodes = append(fnodes, &graph.Node{
  377. Info: graph.NodeInfo{
  378. Name: "???",
  379. Lineno: i,
  380. },
  381. Flat: flat,
  382. Cum: cum,
  383. })
  384. }
  385. return fnodes, filename
  386. }
  387. // openSourceFile opens a source file from a name encoded in a
  388. // profile. File names in a profile after often relative paths, so
  389. // search them in each of the paths in searchPath (or CWD by default),
  390. // and their parents.
  391. func openSourceFile(path, searchPath string) (*os.File, error) {
  392. if filepath.IsAbs(path) {
  393. f, err := os.Open(path)
  394. return f, err
  395. }
  396. // Scan each component of the path
  397. for _, dir := range strings.Split(searchPath, ":") {
  398. // Search up for every parent of each possible path.
  399. for {
  400. filename := filepath.Join(dir, path)
  401. if f, err := os.Open(filename); err == nil {
  402. return f, nil
  403. }
  404. parent := filepath.Dir(dir)
  405. if parent == dir {
  406. break
  407. }
  408. dir = parent
  409. }
  410. }
  411. return nil, fmt.Errorf("Could not find file %s on path %s", path, searchPath)
  412. }
  413. // trimPath cleans up a path by removing prefixes that are commonly
  414. // found on profiles.
  415. func trimPath(path string) string {
  416. basePaths := []string{
  417. "/proc/self/cwd/./",
  418. "/proc/self/cwd/",
  419. }
  420. sPath := filepath.ToSlash(path)
  421. for _, base := range basePaths {
  422. if strings.HasPrefix(sPath, base) {
  423. return filepath.FromSlash(sPath[len(base):])
  424. }
  425. }
  426. return path
  427. }