|
@@ -62,6 +62,7 @@ func printSource(w io.Writer, rpt *Report) error {
|
62
|
62
|
}
|
63
|
63
|
sourcePath = wd
|
64
|
64
|
}
|
|
65
|
+ reader := newSourceReader(sourcePath)
|
65
|
66
|
|
66
|
67
|
fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
|
67
|
68
|
for _, fn := range functions {
|
|
@@ -94,7 +95,7 @@ func printSource(w io.Writer, rpt *Report) error {
|
94
|
95
|
fns := fileNodes[filename]
|
95
|
96
|
flatSum, cumSum := fns.Sum()
|
96
|
97
|
|
97
|
|
- fnodes, _, err := getSourceFromFile(filename, sourcePath, fns, 0, 0)
|
|
98
|
+ fnodes, _, err := getSourceFromFile(filename, reader, fns, 0, 0)
|
98
|
99
|
fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
|
99
|
100
|
fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
|
100
|
101
|
rpt.formatValue(flatSum), rpt.formatValue(cumSum),
|
|
@@ -144,6 +145,7 @@ func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) er
|
144
|
145
|
}
|
145
|
146
|
sourcePath = wd
|
146
|
147
|
}
|
|
148
|
+ reader := newSourceReader(sourcePath)
|
147
|
149
|
|
148
|
150
|
type fileFunction struct {
|
149
|
151
|
fileName, functionName string
|
|
@@ -205,14 +207,14 @@ func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) er
|
205
|
207
|
asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj)
|
206
|
208
|
start, end := sourceCoordinates(asm)
|
207
|
209
|
|
208
|
|
- fnodes, path, err := getSourceFromFile(ff.fileName, sourcePath, fns, start, end)
|
|
210
|
+ fnodes, path, err := getSourceFromFile(ff.fileName, reader, fns, start, end)
|
209
|
211
|
if err != nil {
|
210
|
212
|
fnodes, path = getMissingFunctionSource(ff.fileName, asm, start, end)
|
211
|
213
|
}
|
212
|
214
|
|
213
|
215
|
printFunctionHeader(w, ff.functionName, path, n.Flat, n.Cum, rpt)
|
214
|
216
|
for _, fn := range fnodes {
|
215
|
|
- printFunctionSourceLine(w, fn, asm[fn.Info.Lineno], rpt)
|
|
217
|
+ printFunctionSourceLine(w, fn, asm[fn.Info.Lineno], reader, rpt)
|
216
|
218
|
}
|
217
|
219
|
printFunctionClosing(w)
|
218
|
220
|
}
|
|
@@ -255,9 +257,32 @@ func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj
|
255
|
257
|
var lineno = 0
|
256
|
258
|
var prevline = 0
|
257
|
259
|
for _, an := range anodes {
|
258
|
|
- if filepath.Base(an.file) == srcBase {
|
|
260
|
+ // Do not rely solely on the line number produced by Disasm
|
|
261
|
+ // since it is not what we want in the presence of inlining.
|
|
262
|
+ //
|
|
263
|
+ // E.g., suppose we are printing source code for F and this
|
|
264
|
+ // instruction is from H where F called G called H and both
|
|
265
|
+ // of those calls were inlined. We want to use the line
|
|
266
|
+ // number from F, not from H (which is what Disasm gives us).
|
|
267
|
+ //
|
|
268
|
+ // So find the outer-most linenumber in the source file.
|
|
269
|
+ found := false
|
|
270
|
+ if frames, err := o.file.SourceLine(an.address + o.base); err == nil {
|
|
271
|
+ for i := len(frames) - 1; i >= 0; i-- {
|
|
272
|
+ if filepath.Base(frames[i].File) == srcBase {
|
|
273
|
+ for j := i - 1; j >= 0; j-- {
|
|
274
|
+ an.inlineCalls = append(an.inlineCalls, callID{frames[j].File, frames[j].Line})
|
|
275
|
+ }
|
|
276
|
+ lineno = frames[i].Line
|
|
277
|
+ found = true
|
|
278
|
+ break
|
|
279
|
+ }
|
|
280
|
+ }
|
|
281
|
+ }
|
|
282
|
+ if !found && filepath.Base(an.file) == srcBase {
|
259
|
283
|
lineno = an.line
|
260
|
284
|
}
|
|
285
|
+
|
261
|
286
|
if lineno != 0 {
|
262
|
287
|
if lineno != prevline {
|
263
|
288
|
// This instruction starts a new block
|
|
@@ -322,35 +347,33 @@ func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64,
|
322
|
347
|
}
|
323
|
348
|
|
324
|
349
|
// printFunctionSourceLine prints a source line and the corresponding assembly.
|
325
|
|
-func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyInstruction, rpt *Report) {
|
|
350
|
+func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
|
326
|
351
|
if len(assembly) == 0 {
|
327
|
352
|
fmt.Fprintf(w,
|
328
|
|
- "<span class=line> %6d</span> <span class=nop> %10s %10s %s </span>\n",
|
|
353
|
+ "<span class=line> %6d</span> <span class=nop> %10s %10s %8s %s </span>\n",
|
329
|
354
|
fn.Info.Lineno,
|
330
|
355
|
valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
|
331
|
|
- template.HTMLEscapeString(fn.Info.Name))
|
|
356
|
+ "", template.HTMLEscapeString(fn.Info.Name))
|
332
|
357
|
return
|
333
|
358
|
}
|
334
|
359
|
|
335
|
360
|
fmt.Fprintf(w,
|
336
|
|
- "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %s </span>",
|
|
361
|
+ "<span class=line> %6d</span> <span class=deadsrc> %10s %10s %8s %s </span>",
|
337
|
362
|
fn.Info.Lineno,
|
338
|
363
|
valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
|
339
|
|
- template.HTMLEscapeString(fn.Info.Name))
|
|
364
|
+ "", template.HTMLEscapeString(fn.Info.Name))
|
|
365
|
+ srcIndent := indentation(fn.Info.Name)
|
340
|
366
|
fmt.Fprint(w, "<span class=asm>")
|
|
367
|
+ var curCalls []callID
|
341
|
368
|
for i, an := range assembly {
|
342
|
369
|
if an.startsBlock && i != 0 {
|
343
|
370
|
// Insert a separator between discontiguous blocks.
|
344
|
|
- fmt.Fprintf(w, " %8s %30s\n", "", "⋮")
|
|
371
|
+ fmt.Fprintf(w, " %8s %28s\n", "", "⋮")
|
345
|
372
|
}
|
346
|
373
|
|
347
|
374
|
var fileline string
|
348
|
|
- class := "disasmloc"
|
349
|
375
|
if an.file != "" {
|
350
|
376
|
fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.file), an.line)
|
351
|
|
- if an.line != fn.Info.Lineno {
|
352
|
|
- class = "unimportant"
|
353
|
|
- }
|
354
|
377
|
}
|
355
|
378
|
flat, cum := an.flat, an.cum
|
356
|
379
|
if an.flatDiv != 0 {
|
|
@@ -359,11 +382,30 @@ func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly []assemblyIns
|
359
|
382
|
if an.cumDiv != 0 {
|
360
|
383
|
cum = cum / an.cumDiv
|
361
|
384
|
}
|
362
|
|
- fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=%s>%s</span>\n", "",
|
363
|
|
- valueOrDot(flat, rpt), valueOrDot(cum, rpt),
|
364
|
|
- an.address,
|
365
|
|
- template.HTMLEscapeString(fmt.Sprintf("%-48s", strings.Replace(an.instruction, "\t", " ", -1))),
|
366
|
|
- class,
|
|
385
|
+
|
|
386
|
+ // Print inlined call context.
|
|
387
|
+ for j, c := range an.inlineCalls {
|
|
388
|
+ if j < len(curCalls) && curCalls[j] == c {
|
|
389
|
+ // Skip if same as previous instruction.
|
|
390
|
+ continue
|
|
391
|
+ }
|
|
392
|
+ curCalls = nil
|
|
393
|
+ fname := trimPath(c.file)
|
|
394
|
+ fline, ok := reader.line(fname, c.line)
|
|
395
|
+ if !ok {
|
|
396
|
+ fline = ""
|
|
397
|
+ }
|
|
398
|
+ text := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline)
|
|
399
|
+ fmt.Fprintf(w, " %8s %10s %10s %8s <span class=inlinesrc>%s</span> <span class=unimportant>%s:%d</span>\n",
|
|
400
|
+ "", "", "", "",
|
|
401
|
+ template.HTMLEscapeString(fmt.Sprintf("%-80s", text)),
|
|
402
|
+ template.HTMLEscapeString(filepath.Base(fname)), c.line)
|
|
403
|
+ }
|
|
404
|
+ curCalls = an.inlineCalls
|
|
405
|
+ text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
|
|
406
|
+ fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
|
|
407
|
+ "", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address,
|
|
408
|
+ template.HTMLEscapeString(fmt.Sprintf("%-80s", text)),
|
367
|
409
|
template.HTMLEscapeString(fileline))
|
368
|
410
|
}
|
369
|
411
|
fmt.Fprintln(w, "</span>")
|
|
@@ -382,14 +424,10 @@ func printPageClosing(w io.Writer) {
|
382
|
424
|
// getSourceFromFile collects the sources of a function from a source
|
383
|
425
|
// file and annotates it with the samples in fns. Returns the sources
|
384
|
426
|
// as nodes, using the info.name field to hold the source code.
|
385
|
|
-func getSourceFromFile(file, sourcePath string, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
|
|
427
|
+func getSourceFromFile(file string, reader *sourceReader, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
|
386
|
428
|
file = trimPath(file)
|
387
|
|
- f, err := openSourceFile(file, sourcePath)
|
388
|
|
- if err != nil {
|
389
|
|
- return nil, file, err
|
390
|
|
- }
|
391
|
|
-
|
392
|
429
|
lineNodes := make(map[int]graph.Nodes)
|
|
430
|
+
|
393
|
431
|
// Collect source coordinates from profile.
|
394
|
432
|
const margin = 5 // Lines before first/after last sample.
|
395
|
433
|
if start == 0 {
|
|
@@ -419,36 +457,28 @@ func getSourceFromFile(file, sourcePath string, fns graph.Nodes, start, end int)
|
419
|
457
|
}
|
420
|
458
|
lineNodes[lineno] = append(lineNodes[lineno], n)
|
421
|
459
|
}
|
|
460
|
+ if start < 1 {
|
|
461
|
+ start = 1
|
|
462
|
+ }
|
422
|
463
|
|
423
|
464
|
var src graph.Nodes
|
424
|
|
- buf := bufio.NewReader(f)
|
425
|
|
- lineno := 1
|
426
|
|
- for {
|
427
|
|
- line, err := buf.ReadString('\n')
|
428
|
|
- if err != nil {
|
429
|
|
- if err != io.EOF {
|
430
|
|
- return nil, file, err
|
431
|
|
- }
|
432
|
|
- if line == "" {
|
433
|
|
- break
|
434
|
|
- }
|
435
|
|
- }
|
436
|
|
- if lineno >= start {
|
437
|
|
- flat, cum := lineNodes[lineno].Sum()
|
438
|
|
-
|
439
|
|
- src = append(src, &graph.Node{
|
440
|
|
- Info: graph.NodeInfo{
|
441
|
|
- Name: strings.TrimRight(line, "\n"),
|
442
|
|
- Lineno: lineno,
|
443
|
|
- },
|
444
|
|
- Flat: flat,
|
445
|
|
- Cum: cum,
|
446
|
|
- })
|
447
|
|
- }
|
448
|
|
- lineno++
|
449
|
|
- if lineno > end {
|
|
465
|
+ for lineno := start; lineno <= end; lineno++ {
|
|
466
|
+ line, ok := reader.line(file, lineno)
|
|
467
|
+ if !ok {
|
450
|
468
|
break
|
451
|
469
|
}
|
|
470
|
+ flat, cum := lineNodes[lineno].Sum()
|
|
471
|
+ src = append(src, &graph.Node{
|
|
472
|
+ Info: graph.NodeInfo{
|
|
473
|
+ Name: strings.TrimRight(line, "\n"),
|
|
474
|
+ Lineno: lineno,
|
|
475
|
+ },
|
|
476
|
+ Flat: flat,
|
|
477
|
+ Cum: cum,
|
|
478
|
+ })
|
|
479
|
+ }
|
|
480
|
+ if err := reader.fileError(file); err != nil {
|
|
481
|
+ return nil, file, err
|
452
|
482
|
}
|
453
|
483
|
return src, file, nil
|
454
|
484
|
}
|
|
@@ -483,6 +513,57 @@ func getMissingFunctionSource(filename string, asm map[int][]assemblyInstruction
|
483
|
513
|
return fnodes, filename
|
484
|
514
|
}
|
485
|
515
|
|
|
516
|
+// sourceReader provides access to source code with caching of file contents.
|
|
517
|
+type sourceReader struct {
|
|
518
|
+ searchPath string
|
|
519
|
+
|
|
520
|
+ // files maps from path name to a list of lines.
|
|
521
|
+ // files[*][0] is unused since line numbering starts at 1.
|
|
522
|
+ files map[string][]string
|
|
523
|
+
|
|
524
|
+ // errors collects errors encountered per file. These errors are
|
|
525
|
+ // consulted before returning out of these module.
|
|
526
|
+ errors map[string]error
|
|
527
|
+}
|
|
528
|
+
|
|
529
|
+func newSourceReader(searchPath string) *sourceReader {
|
|
530
|
+ return &sourceReader{
|
|
531
|
+ searchPath,
|
|
532
|
+ make(map[string][]string),
|
|
533
|
+ make(map[string]error),
|
|
534
|
+ }
|
|
535
|
+}
|
|
536
|
+
|
|
537
|
+func (reader *sourceReader) fileError(path string) error {
|
|
538
|
+ return reader.errors[path]
|
|
539
|
+}
|
|
540
|
+
|
|
541
|
+func (reader *sourceReader) line(path string, lineno int) (string, bool) {
|
|
542
|
+ lines, ok := reader.files[path]
|
|
543
|
+ if !ok {
|
|
544
|
+ // Read and cache file contents.
|
|
545
|
+ lines = []string{""} // Skip 0th line
|
|
546
|
+ f, err := openSourceFile(path, reader.searchPath)
|
|
547
|
+ if err != nil {
|
|
548
|
+ reader.errors[path] = err
|
|
549
|
+ } else {
|
|
550
|
+ s := bufio.NewScanner(f)
|
|
551
|
+ for s.Scan() {
|
|
552
|
+ lines = append(lines, s.Text())
|
|
553
|
+ }
|
|
554
|
+ f.Close()
|
|
555
|
+ if s.Err() != nil {
|
|
556
|
+ reader.errors[path] = err
|
|
557
|
+ }
|
|
558
|
+ }
|
|
559
|
+ reader.files[path] = lines
|
|
560
|
+ }
|
|
561
|
+ if lineno <= 0 || lineno >= len(lines) {
|
|
562
|
+ return "", false
|
|
563
|
+ }
|
|
564
|
+ return lines[lineno], true
|
|
565
|
+}
|
|
566
|
+
|
486
|
567
|
// openSourceFile opens a source file from a name encoded in a
|
487
|
568
|
// profile. File names in a profile after often relative paths, so
|
488
|
569
|
// search them in each of the paths in searchPath (or CWD by default),
|
|
@@ -529,3 +610,20 @@ func trimPath(path string) string {
|
529
|
610
|
}
|
530
|
611
|
return path
|
531
|
612
|
}
|
|
613
|
+
|
|
614
|
+func indentation(line string) int {
|
|
615
|
+ column := 0
|
|
616
|
+ for _, c := range line {
|
|
617
|
+ if c == ' ' {
|
|
618
|
+ column++
|
|
619
|
+ } else if c == '\t' {
|
|
620
|
+ column++
|
|
621
|
+ for column%8 != 0 {
|
|
622
|
+ column++
|
|
623
|
+ }
|
|
624
|
+ } else {
|
|
625
|
+ break
|
|
626
|
+ }
|
|
627
|
+ }
|
|
628
|
+ return column
|
|
629
|
+}
|