github.com/razvanm/vanadium-go-1.3@v0.0.0-20160721203343-4a65068e5915/src/cmd/pprof/internal/report/source.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package report 6 7 // This file contains routines related to the generation of annotated 8 // source listings. 9 10 import ( 11 "bufio" 12 "fmt" 13 "html/template" 14 "io" 15 "os" 16 "path/filepath" 17 "sort" 18 "strconv" 19 "strings" 20 21 "cmd/pprof/internal/plugin" 22 ) 23 24 // printSource prints an annotated source listing, include all 25 // functions with samples that match the regexp rpt.options.symbol. 26 // The sources are sorted by function name and then by filename to 27 // eliminate potential nondeterminism. 28 func printSource(w io.Writer, rpt *Report) error { 29 o := rpt.options 30 g, err := newGraph(rpt) 31 if err != nil { 32 return err 33 } 34 35 // Identify all the functions that match the regexp provided. 36 // Group nodes for each matching function. 37 var functions nodes 38 functionNodes := make(map[string]nodes) 39 for _, n := range g.ns { 40 if !o.Symbol.MatchString(n.info.name) { 41 continue 42 } 43 if functionNodes[n.info.name] == nil { 44 functions = append(functions, n) 45 } 46 functionNodes[n.info.name] = append(functionNodes[n.info.name], n) 47 } 48 functions.sort(nameOrder) 49 50 fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total)) 51 for _, fn := range functions { 52 name := fn.info.name 53 54 // Identify all the source files associated to this function. 55 // Group nodes for each source file. 56 var sourceFiles nodes 57 fileNodes := make(map[string]nodes) 58 for _, n := range functionNodes[name] { 59 if n.info.file == "" { 60 continue 61 } 62 if fileNodes[n.info.file] == nil { 63 sourceFiles = append(sourceFiles, n) 64 } 65 fileNodes[n.info.file] = append(fileNodes[n.info.file], n) 66 } 67 68 if len(sourceFiles) == 0 { 69 fmt.Printf("No source information for %s\n", name) 70 continue 71 } 72 73 sourceFiles.sort(fileOrder) 74 75 // Print each file associated with this function. 76 for _, fl := range sourceFiles { 77 filename := fl.info.file 78 fns := fileNodes[filename] 79 flatSum, cumSum := sumNodes(fns) 80 81 fnodes, path, err := getFunctionSource(name, filename, fns, 0, 0) 82 fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, path) 83 fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n", 84 rpt.formatValue(flatSum), rpt.formatValue(cumSum), 85 percentage(cumSum, rpt.total)) 86 87 if err != nil { 88 fmt.Fprintf(w, " Error: %v\n", err) 89 continue 90 } 91 92 for _, fn := range fnodes { 93 fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt), fn.info.lineno, fn.info.name) 94 } 95 } 96 } 97 return nil 98 } 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, err := newGraph(rpt) 105 if err != nil { 106 return err 107 } 108 109 // If the regexp source can be parsed as an address, also match 110 // functions that land on that address. 111 var address *uint64 112 if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil { 113 address = &hex 114 } 115 116 // Extract interesting symbols from binary files in the profile and 117 // classify samples per symbol. 118 symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj) 119 symNodes := nodesPerSymbol(g.ns, symbols) 120 121 // Sort symbols for printing. 122 var syms objSymbols 123 for s := range symNodes { 124 syms = append(syms, s) 125 } 126 sort.Sort(syms) 127 128 if len(syms) == 0 { 129 return fmt.Errorf("no samples found on routines matching: %s", o.Symbol.String()) 130 } 131 132 printHeader(w, rpt) 133 for _, s := range syms { 134 name := s.sym.Name[0] 135 // Identify sources associated to a symbol by examining 136 // symbol samples. Classify samples per source file. 137 var sourceFiles nodes 138 fileNodes := make(map[string]nodes) 139 for _, n := range symNodes[s] { 140 if n.info.file == "" { 141 continue 142 } 143 if fileNodes[n.info.file] == nil { 144 sourceFiles = append(sourceFiles, n) 145 } 146 fileNodes[n.info.file] = append(fileNodes[n.info.file], n) 147 } 148 149 if len(sourceFiles) == 0 { 150 fmt.Printf("No source information for %s\n", name) 151 continue 152 } 153 154 sourceFiles.sort(fileOrder) 155 156 // Print each file associated with this function. 157 for _, fl := range sourceFiles { 158 filename := fl.info.file 159 fns := fileNodes[filename] 160 161 asm := assemblyPerSourceLine(symbols, fns, filename, obj) 162 start, end := sourceCoordinates(asm) 163 164 fnodes, path, err := getFunctionSource(name, filename, fns, start, end) 165 if err != nil { 166 fnodes, path = getMissingFunctionSource(filename, asm, start, end) 167 } 168 169 flatSum, cumSum := sumNodes(fnodes) 170 printFunctionHeader(w, name, path, flatSum, cumSum, rpt) 171 for _, fn := range fnodes { 172 printFunctionSourceLine(w, fn, asm[fn.info.lineno], rpt) 173 } 174 printFunctionClosing(w) 175 } 176 } 177 printPageClosing(w) 178 return nil 179 } 180 181 // sourceCoordinates returns the lowest and highest line numbers from 182 // a set of assembly statements. 183 func sourceCoordinates(asm map[int]nodes) (start, end int) { 184 for l := range asm { 185 if start == 0 || l < start { 186 start = l 187 } 188 if end == 0 || l > end { 189 end = l 190 } 191 } 192 return start, end 193 } 194 195 // assemblyPerSourceLine disassembles the binary containing a symbol 196 // and classifies the assembly instructions according to its 197 // corresponding source line, annotating them with a set of samples. 198 func assemblyPerSourceLine(objSyms []*objSymbol, rs nodes, src string, obj plugin.ObjTool) map[int]nodes { 199 assembly := make(map[int]nodes) 200 // Identify symbol to use for this collection of samples. 201 o := findMatchingSymbol(objSyms, rs) 202 if o == nil { 203 return assembly 204 } 205 206 // Extract assembly for matched symbol 207 insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End) 208 if err != nil { 209 return assembly 210 } 211 212 srcBase := filepath.Base(src) 213 anodes := annotateAssembly(insns, rs, o.base) 214 var lineno = 0 215 for _, an := range anodes { 216 if filepath.Base(an.info.file) == srcBase { 217 lineno = an.info.lineno 218 } 219 if lineno != 0 { 220 assembly[lineno] = append(assembly[lineno], an) 221 } 222 } 223 224 return assembly 225 } 226 227 // findMatchingSymbol looks for the symbol that corresponds to a set 228 // of samples, by comparing their addresses. 229 func findMatchingSymbol(objSyms []*objSymbol, ns nodes) *objSymbol { 230 for _, n := range ns { 231 for _, o := range objSyms { 232 if filepath.Base(o.sym.File) == n.info.objfile && 233 o.sym.Start <= n.info.address-o.base && 234 n.info.address-o.base <= o.sym.End { 235 return o 236 } 237 } 238 } 239 return nil 240 } 241 242 // printHeader prints the page header for a weblist report. 243 func printHeader(w io.Writer, rpt *Report) { 244 fmt.Fprintln(w, weblistPageHeader) 245 246 var labels []string 247 for _, l := range legendLabels(rpt) { 248 labels = append(labels, template.HTMLEscapeString(l)) 249 } 250 251 fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`, 252 strings.Join(labels, "<br>\n"), 253 rpt.formatValue(rpt.total), 254 ) 255 } 256 257 // printFunctionHeader prints a function header for a weblist report. 258 func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) { 259 fmt.Fprintf(w, `<h1>%s</h1>%s 260 <pre onClick="pprof_toggle_asm()"> 261 Total: %10s %10s (flat, cum) %s 262 `, 263 template.HTMLEscapeString(name), template.HTMLEscapeString(path), 264 rpt.formatValue(flatSum), rpt.formatValue(cumSum), 265 percentage(cumSum, rpt.total)) 266 } 267 268 // printFunctionSourceLine prints a source line and the corresponding assembly. 269 func printFunctionSourceLine(w io.Writer, fn *node, assembly nodes, rpt *Report) { 270 if len(assembly) == 0 { 271 fmt.Fprintf(w, 272 "<span class=line> %6d</span> <span class=nop> %10s %10s %s </span>\n", 273 fn.info.lineno, 274 valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt), 275 template.HTMLEscapeString(fn.info.name)) 276 return 277 } 278 279 fmt.Fprintf(w, 280 "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %s </span>", 281 fn.info.lineno, 282 valueOrDot(fn.flat, rpt), valueOrDot(fn.cum, rpt), 283 template.HTMLEscapeString(fn.info.name)) 284 fmt.Fprint(w, "<span class=asm>") 285 for _, an := range assembly { 286 var fileline string 287 class := "disasmloc" 288 if an.info.file != "" { 289 fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.info.file), an.info.lineno) 290 if an.info.lineno != fn.info.lineno { 291 class = "unimportant" 292 } 293 } 294 fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "", 295 valueOrDot(an.flat, rpt), valueOrDot(an.cum, rpt), 296 an.info.address, 297 template.HTMLEscapeString(an.info.name), 298 class, 299 template.HTMLEscapeString(fileline)) 300 } 301 fmt.Fprintln(w, "</span>") 302 } 303 304 // printFunctionClosing prints the end of a function in a weblist report. 305 func printFunctionClosing(w io.Writer) { 306 fmt.Fprintln(w, "</pre>") 307 } 308 309 // printPageClosing prints the end of the page in a weblist report. 310 func printPageClosing(w io.Writer) { 311 fmt.Fprintln(w, weblistPageClosing) 312 } 313 314 // getFunctionSource 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 getFunctionSource(fun, file string, fns nodes, start, end int) (nodes, string, error) { 318 f, file, err := adjustSourcePath(file) 319 if err != nil { 320 return nil, file, err 321 } 322 323 lineNodes := make(map[int]nodes) 324 325 // Collect source coordinates from profile. 326 const margin = 5 // Lines before first/after last sample. 327 if start == 0 { 328 if fns[0].info.startLine != 0 { 329 start = fns[0].info.startLine 330 } else { 331 start = fns[0].info.lineno - margin 332 } 333 } else { 334 start -= margin 335 } 336 if end == 0 { 337 end = fns[0].info.lineno 338 } 339 end += margin 340 for _, n := range fns { 341 lineno := n.info.lineno 342 nodeStart := n.info.startLine 343 if nodeStart == 0 { 344 nodeStart = lineno - margin 345 } 346 nodeEnd := lineno + margin 347 if nodeStart < start { 348 start = nodeStart 349 } else if nodeEnd > end { 350 end = nodeEnd 351 } 352 lineNodes[lineno] = append(lineNodes[lineno], n) 353 } 354 355 var src nodes 356 buf := bufio.NewReader(f) 357 lineno := 1 358 for { 359 line, err := buf.ReadString('\n') 360 if err != nil { 361 if line == "" || err != io.EOF { 362 return nil, file, err 363 } 364 } 365 if lineno >= start { 366 flat, cum := sumNodes(lineNodes[lineno]) 367 368 src = append(src, &node{ 369 info: 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 385 // getMissingFunctionSource creates a dummy function body to point to 386 // the source file and annotates it with the samples in asm. 387 func getMissingFunctionSource(filename string, asm map[int]nodes, start, end int) (nodes, string) { 388 var fnodes nodes 389 for i := start; i <= end; i++ { 390 lrs := asm[i] 391 if len(lrs) == 0 { 392 continue 393 } 394 flat, cum := sumNodes(lrs) 395 fnodes = append(fnodes, &node{ 396 info: nodeInfo{ 397 name: "???", 398 lineno: i, 399 }, 400 flat: flat, 401 cum: cum, 402 }) 403 } 404 return fnodes, filename 405 } 406 407 // adjustSourcePath adjusts the pathe for a source file by trimmming 408 // known prefixes and searching for the file on all parents of the 409 // current working dir. 410 func adjustSourcePath(path string) (*os.File, string, error) { 411 path = trimPath(path) 412 f, err := os.Open(path) 413 if err == nil { 414 return f, path, nil 415 } 416 417 if dir, wderr := os.Getwd(); wderr == nil { 418 for { 419 parent := filepath.Dir(dir) 420 if parent == dir { 421 break 422 } 423 if f, err := os.Open(filepath.Join(parent, path)); err == nil { 424 return f, filepath.Join(parent, path), nil 425 } 426 427 dir = parent 428 } 429 } 430 431 return nil, path, err 432 } 433 434 // trimPath cleans up a path by removing prefixes that are commonly 435 // found on profiles. 436 func trimPath(path string) string { 437 basePaths := []string{ 438 "/proc/self/cwd/./", 439 "/proc/self/cwd/", 440 } 441 442 sPath := filepath.ToSlash(path) 443 444 for _, base := range basePaths { 445 if strings.HasPrefix(sPath, base) { 446 return filepath.FromSlash(sPath[len(base):]) 447 } 448 } 449 return path 450 }