Browse Source

weblist output disassembly now contains inlined source. (#235)

weblist output disassembly now contains inlined source.

In addition, fixed the line number assigned to instructions from inlined calls.

As an illustration, see the before and after output for a "pprof -weblist" command for a simple function:

[before](https://ghemawat.github.io/scratch/tmp/list1.html)
[after](https://ghemawat.github.io/scratch/tmp/list2.html)

Note that in the "before" page, the 7.26 second line cannot be expanded, but in the "after" page clicking on it reveals a wealth of information.
Sanjay Ghemawat 7 years ago
parent
commit
8494129efa

+ 31
- 37
internal/driver/testdata/pprof.cpu.flat.addresses.weblist View File

15
 .legend {
15
 .legend {
16
   font-size: 1.25em;
16
   font-size: 1.25em;
17
 }
17
 }
18
-.line {
19
-color: #aaaaaa;
18
+.line, .nop, .unimportant {
19
+  color: #aaaaaa;
20
 }
20
 }
21
-.nop {
22
-color: #aaaaaa;
23
-}
24
-.unimportant {
25
-color: #cccccc;
26
-}
27
-.disasmloc {
28
-color: #000000;
21
+.inlinesrc {
22
+  color: #000066;
29
 }
23
 }
30
 .deadsrc {
24
 .deadsrc {
31
 cursor: pointer;
25
 cursor: pointer;
70
 Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h1>line1000</h1>testdata/file1000.src
64
 Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h1>line1000</h1>testdata/file1000.src
71
 <pre onClick="pprof_toggle_asm(event)">
65
 <pre onClick="pprof_toggle_asm(event)">
72
   Total:       1.10s      1.10s (flat, cum) 98.21%
66
   Total:       1.10s      1.10s (flat, cum) 98.21%
73
-<span class=line>      1</span> <span class=deadsrc>       1.10s      1.10s line1 </span><span class=asm>               1.10s      1.10s     1000: instruction one                                  <span class=disasmloc>file1000.src:1</span>
74
-                   .          .     1001: instruction two                                  <span class=disasmloc>file1000.src:1</span>
75
-                                       
76
-                   .          .     1003: instruction four                                 <span class=disasmloc>file1000.src:1</span>
67
+<span class=line>      1</span> <span class=deadsrc>       1.10s      1.10s           line1 </span><span class=asm>               1.10s      1.10s     1000:     instruction one                                                              <span class=unimportant>file1000.src:1</span>
68
+                   .          .     1001:     instruction two                                                              <span class=unimportant>file1000.src:1</span>
69
+                                     ⋮
70
+                   .          .     1003:     instruction four                                                             <span class=unimportant>file1000.src:1</span>
77
 </span>
71
 </span>
78
-<span class=line>      2</span> <span class=deadsrc>           .          . line2 </span><span class=asm>                   .          .     1002: instruction three                                <span class=disasmloc>file1000.src:2</span>
72
+<span class=line>      2</span> <span class=deadsrc>           .          .           line2 </span><span class=asm>                   .          .     1002:     instruction three                                                            <span class=unimportant>file1000.src:2</span>
79
 </span>
73
 </span>
80
-<span class=line>      3</span> <span class=nop>           .          . line3 </span>
81
-<span class=line>      4</span> <span class=nop>           .          . line4 </span>
82
-<span class=line>      5</span> <span class=nop>           .          . line5 </span>
83
-<span class=line>      6</span> <span class=nop>           .          . line6 </span>
84
-<span class=line>      7</span> <span class=nop>           .          . line7 </span>
74
+<span class=line>      3</span> <span class=nop>           .          .           line3 </span>
75
+<span class=line>      4</span> <span class=nop>           .          .           line4 </span>
76
+<span class=line>      5</span> <span class=nop>           .          .           line5 </span>
77
+<span class=line>      6</span> <span class=nop>           .          .           line6 </span>
78
+<span class=line>      7</span> <span class=nop>           .          .           line7 </span>
85
 </pre>
79
 </pre>
86
 <h1>line3000</h1>testdata/file3000.src
80
 <h1>line3000</h1>testdata/file3000.src
87
 <pre onClick="pprof_toggle_asm(event)">
81
 <pre onClick="pprof_toggle_asm(event)">
88
   Total:        10ms      1.12s (flat, cum)   100%
82
   Total:        10ms      1.12s (flat, cum)   100%
89
-<span class=line>      1</span> <span class=nop>           .          . line1 </span>
90
-<span class=line>      2</span> <span class=nop>           .          . line2 </span>
91
-<span class=line>      3</span> <span class=nop>           .          . line3 </span>
92
-<span class=line>      4</span> <span class=nop>           .          . line4 </span>
93
-<span class=line>      5</span> <span class=nop>           .          . line5 </span>
94
-<span class=line>      6</span> <span class=deadsrc>        10ms      1.01s line6 </span><span class=asm>                10ms      1.01s     3000: instruction one                                  <span class=disasmloc>file3000.src:6</span>
83
+<span class=line>      1</span> <span class=nop>           .          .           line1 </span>
84
+<span class=line>      2</span> <span class=nop>           .          .           line2 </span>
85
+<span class=line>      3</span> <span class=nop>           .          .           line3 </span>
86
+<span class=line>      4</span> <span class=nop>           .          .           line4 </span>
87
+<span class=line>      5</span> <span class=nop>           .          .           line5 </span>
88
+<span class=line>      6</span> <span class=deadsrc>        10ms      1.01s           line6 </span><span class=asm>                10ms      1.01s     3000:     instruction one                                                              <span class=unimportant>file3000.src:6</span>
95
 </span>
89
 </span>
96
-<span class=line>      7</span> <span class=nop>           .          . line7 </span>
97
-<span class=line>      8</span> <span class=nop>           .          . line8 </span>
98
-<span class=line>      9</span> <span class=deadsrc>           .      110ms line9 </span><span class=asm>                   .      100ms     3001: instruction two                                  <span class=disasmloc>file3000.src:9</span>
99
-                   .       10ms     3002: instruction three                                <span class=disasmloc>file3000.src:9</span>
100
-                   .          .     3003: instruction four                                 <span class=disasmloc></span>
101
-                   .          .     3004: instruction five                                 <span class=disasmloc></span>
90
+<span class=line>      7</span> <span class=nop>           .          .           line7 </span>
91
+<span class=line>      8</span> <span class=nop>           .          .           line8 </span>
92
+<span class=line>      9</span> <span class=deadsrc>           .      110ms           line9 </span><span class=asm>                   .      100ms     3001:     instruction two                                                              <span class=unimportant>file3000.src:9</span>
93
+                   .       10ms     3002:     instruction three                                                            <span class=unimportant>file3000.src:9</span>
94
+                   .          .     3003:     instruction four                                                             <span class=unimportant></span>
95
+                   .          .     3004:     instruction five                                                             <span class=unimportant></span>
102
 </span>
96
 </span>
103
-<span class=line>     10</span> <span class=nop>           .          . line0 </span>
104
-<span class=line>     11</span> <span class=nop>           .          . line1 </span>
105
-<span class=line>     12</span> <span class=nop>           .          . line2 </span>
106
-<span class=line>     13</span> <span class=nop>           .          . line3 </span>
107
-<span class=line>     14</span> <span class=nop>           .          . line4 </span>
97
+<span class=line>     10</span> <span class=nop>           .          .           line0 </span>
98
+<span class=line>     11</span> <span class=nop>           .          .           line1 </span>
99
+<span class=line>     12</span> <span class=nop>           .          .           line2 </span>
100
+<span class=line>     13</span> <span class=nop>           .          .           line3 </span>
101
+<span class=line>     14</span> <span class=nop>           .          .           line4 </span>
108
 </pre>
102
 </pre>
109
 
103
 
110
 </body>
104
 </body>

+ 1
- 1
internal/driver/webui_test.go View File

70
 		{"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true},
70
 		{"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true},
71
 		{"/top", []string{`"Name":"F2","InlineLabel":"","Flat":200,"Cum":300,"FlatFormat":"200ms","CumFormat":"300ms"}`}, false},
71
 		{"/top", []string{`"Name":"F2","InlineLabel":"","Flat":200,"Cum":300,"FlatFormat":"200ms","CumFormat":"300ms"}`}, false},
72
 		{"/source?f=" + url.QueryEscape("F[12]"),
72
 		{"/source?f=" + url.QueryEscape("F[12]"),
73
-			[]string{"F1", "F2", "300ms line1"}, false},
73
+			[]string{"F1", "F2", "300ms +line1"}, false},
74
 		{"/peek?f=" + url.QueryEscape("F[12]"),
74
 		{"/peek?f=" + url.QueryEscape("F[12]"),
75
 			[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},
75
 			[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},
76
 		{"/disasm?f=" + url.QueryEscape("F[12]"),
76
 		{"/disasm?f=" + url.QueryEscape("F[12]"),

+ 8
- 0
internal/report/report.go View File

516
 				&objSymbol{
516
 				&objSymbol{
517
 					sym:  ms,
517
 					sym:  ms,
518
 					base: base,
518
 					base: base,
519
+					file: f,
519
 				},
520
 				},
520
 			)
521
 			)
521
 		}
522
 		}
530
 type objSymbol struct {
531
 type objSymbol struct {
531
 	sym  *plugin.Sym
532
 	sym  *plugin.Sym
532
 	base uint64
533
 	base uint64
534
+	file plugin.ObjFile
533
 }
535
 }
534
 
536
 
535
 // orderSyms is a wrapper type to sort []*objSymbol by a supplied comparator.
537
 // orderSyms is a wrapper type to sort []*objSymbol by a supplied comparator.
566
 	flat, cum       int64
568
 	flat, cum       int64
567
 	flatDiv, cumDiv int64
569
 	flatDiv, cumDiv int64
568
 	startsBlock     bool
570
 	startsBlock     bool
571
+	inlineCalls     []callID
572
+}
573
+
574
+type callID struct {
575
+	file string
576
+	line int
569
 }
577
 }
570
 
578
 
571
 func (a *assemblyInstruction) flatValue() int64 {
579
 func (a *assemblyInstruction) flatValue() int64 {

+ 149
- 51
internal/report/source.go View File

62
 		}
62
 		}
63
 		sourcePath = wd
63
 		sourcePath = wd
64
 	}
64
 	}
65
+	reader := newSourceReader(sourcePath)
65
 
66
 
66
 	fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
67
 	fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
67
 	for _, fn := range functions {
68
 	for _, fn := range functions {
94
 			fns := fileNodes[filename]
95
 			fns := fileNodes[filename]
95
 			flatSum, cumSum := fns.Sum()
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
 			fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
99
 			fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
99
 			fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
100
 			fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
100
 				rpt.formatValue(flatSum), rpt.formatValue(cumSum),
101
 				rpt.formatValue(flatSum), rpt.formatValue(cumSum),
144
 		}
145
 		}
145
 		sourcePath = wd
146
 		sourcePath = wd
146
 	}
147
 	}
148
+	reader := newSourceReader(sourcePath)
147
 
149
 
148
 	type fileFunction struct {
150
 	type fileFunction struct {
149
 		fileName, functionName string
151
 		fileName, functionName string
205
 		asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj)
207
 		asm := assemblyPerSourceLine(symbols, fns, ff.fileName, obj)
206
 		start, end := sourceCoordinates(asm)
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
 		if err != nil {
211
 		if err != nil {
210
 			fnodes, path = getMissingFunctionSource(ff.fileName, asm, start, end)
212
 			fnodes, path = getMissingFunctionSource(ff.fileName, asm, start, end)
211
 		}
213
 		}
212
 
214
 
213
 		printFunctionHeader(w, ff.functionName, path, n.Flat, n.Cum, rpt)
215
 		printFunctionHeader(w, ff.functionName, path, n.Flat, n.Cum, rpt)
214
 		for _, fn := range fnodes {
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
 		printFunctionClosing(w)
219
 		printFunctionClosing(w)
218
 	}
220
 	}
255
 	var lineno = 0
257
 	var lineno = 0
256
 	var prevline = 0
258
 	var prevline = 0
257
 	for _, an := range anodes {
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
 			lineno = an.line
283
 			lineno = an.line
260
 		}
284
 		}
285
+
261
 		if lineno != 0 {
286
 		if lineno != 0 {
262
 			if lineno != prevline {
287
 			if lineno != prevline {
263
 				// This instruction starts a new block
288
 				// This instruction starts a new block
322
 }
347
 }
323
 
348
 
324
 // printFunctionSourceLine prints a source line and the corresponding assembly.
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
 	if len(assembly) == 0 {
351
 	if len(assembly) == 0 {
327
 		fmt.Fprintf(w,
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
 			fn.Info.Lineno,
354
 			fn.Info.Lineno,
330
 			valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
355
 			valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
331
-			template.HTMLEscapeString(fn.Info.Name))
356
+			"", template.HTMLEscapeString(fn.Info.Name))
332
 		return
357
 		return
333
 	}
358
 	}
334
 
359
 
335
 	fmt.Fprintf(w,
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
 		fn.Info.Lineno,
362
 		fn.Info.Lineno,
338
 		valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
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
 	fmt.Fprint(w, "<span class=asm>")
366
 	fmt.Fprint(w, "<span class=asm>")
367
+	var curCalls []callID
341
 	for i, an := range assembly {
368
 	for i, an := range assembly {
342
 		if an.startsBlock && i != 0 {
369
 		if an.startsBlock && i != 0 {
343
 			// Insert a separator between discontiguous blocks.
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
 		var fileline string
374
 		var fileline string
348
-		class := "disasmloc"
349
 		if an.file != "" {
375
 		if an.file != "" {
350
 			fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.file), an.line)
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
 		flat, cum := an.flat, an.cum
378
 		flat, cum := an.flat, an.cum
356
 		if an.flatDiv != 0 {
379
 		if an.flatDiv != 0 {
359
 		if an.cumDiv != 0 {
382
 		if an.cumDiv != 0 {
360
 			cum = cum / an.cumDiv
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
 			template.HTMLEscapeString(fileline))
409
 			template.HTMLEscapeString(fileline))
368
 	}
410
 	}
369
 	fmt.Fprintln(w, "</span>")
411
 	fmt.Fprintln(w, "</span>")
382
 // getSourceFromFile collects the sources of a function from a source
424
 // getSourceFromFile collects the sources of a function from a source
383
 // file and annotates it with the samples in fns. Returns the sources
425
 // file and annotates it with the samples in fns. Returns the sources
384
 // as nodes, using the info.name field to hold the source code.
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
 	file = trimPath(file)
428
 	file = trimPath(file)
387
-	f, err := openSourceFile(file, sourcePath)
388
-	if err != nil {
389
-		return nil, file, err
390
-	}
391
-
392
 	lineNodes := make(map[int]graph.Nodes)
429
 	lineNodes := make(map[int]graph.Nodes)
430
+
393
 	// Collect source coordinates from profile.
431
 	// Collect source coordinates from profile.
394
 	const margin = 5 // Lines before first/after last sample.
432
 	const margin = 5 // Lines before first/after last sample.
395
 	if start == 0 {
433
 	if start == 0 {
419
 		}
457
 		}
420
 		lineNodes[lineno] = append(lineNodes[lineno], n)
458
 		lineNodes[lineno] = append(lineNodes[lineno], n)
421
 	}
459
 	}
460
+	if start < 1 {
461
+		start = 1
462
+	}
422
 
463
 
423
 	var src graph.Nodes
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
 			break
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
 	return src, file, nil
483
 	return src, file, nil
454
 }
484
 }
483
 	return fnodes, filename
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
 // openSourceFile opens a source file from a name encoded in a
567
 // openSourceFile opens a source file from a name encoded in a
487
 // profile. File names in a profile after often relative paths, so
568
 // profile. File names in a profile after often relative paths, so
488
 // search them in each of the paths in searchPath (or CWD by default),
569
 // search them in each of the paths in searchPath (or CWD by default),
529
 	}
610
 	}
530
 	return path
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
+}

+ 4
- 10
internal/report/source_html.go View File

35
 .legend {
35
 .legend {
36
   font-size: 1.25em;
36
   font-size: 1.25em;
37
 }
37
 }
38
-.line {
39
-color: #aaaaaa;
38
+.line, .nop, .unimportant {
39
+  color: #aaaaaa;
40
 }
40
 }
41
-.nop {
42
-color: #aaaaaa;
43
-}
44
-.unimportant {
45
-color: #cccccc;
46
-}
47
-.disasmloc {
48
-color: #000000;
41
+.inlinesrc {
42
+  color: #000066;
49
 }
43
 }
50
 .deadsrc {
44
 .deadsrc {
51
 cursor: pointer;
45
 cursor: pointer;

+ 89
- 0
internal/report/source_test.go View File

1
+package report
2
+
3
+import (
4
+	"bytes"
5
+	"os"
6
+	"path/filepath"
7
+	"regexp"
8
+	"runtime"
9
+	"strings"
10
+	"testing"
11
+
12
+	"github.com/google/pprof/internal/binutils"
13
+	"github.com/google/pprof/profile"
14
+)
15
+
16
+func TestWebList(t *testing.T) {
17
+	if runtime.GOOS != "linux" {
18
+		t.Skip("weblist only tested on linux")
19
+	}
20
+
21
+	cpu := readProfile(filepath.Join("testdata", "sample.cpu"), t)
22
+	rpt := New(cpu, &Options{
23
+		OutputFormat: WebList,
24
+		Symbol:       regexp.MustCompile("busyLoop"),
25
+		SampleValue:  func(v []int64) int64 { return v[1] },
26
+		SampleUnit:   cpu.SampleType[1].Unit,
27
+	})
28
+	buf := bytes.NewBuffer(nil)
29
+	if err := Generate(buf, rpt, &binutils.Binutils{}); err != nil {
30
+		t.Fatalf("could not generate weblist: %v", err)
31
+	}
32
+	output := buf.String()
33
+
34
+	for _, expect := range []string{"func busyLoop", "callq", "math.Abs"} {
35
+		if !strings.Contains(output, expect) {
36
+			t.Errorf("weblist output does not contain '%s':\n%s", expect, output)
37
+		}
38
+	}
39
+}
40
+
41
+func TestIndentation(t *testing.T) {
42
+	for _, c := range []struct {
43
+		str    string
44
+		indent int
45
+	}{
46
+		{"", 0},
47
+		{"foobar", 0},
48
+		{"  foo", 2},
49
+		{"\tfoo", 8},
50
+		{"\t foo", 9},
51
+		{"  \tfoo", 8},
52
+		{"       \tfoo", 8},
53
+		{"        \tfoo", 16},
54
+	} {
55
+		if n := indentation(c.str); n != c.indent {
56
+			t.Errorf("indentation for %v: expect %d, got %d\n", c.str, c.indent, n)
57
+		}
58
+	}
59
+}
60
+
61
+func readProfile(fname string, t *testing.T) *profile.Profile {
62
+	file, err := os.Open(fname)
63
+	if err != nil {
64
+		t.Fatalf("%s: could not open profile: %v", fname, err)
65
+	}
66
+	defer file.Close()
67
+	p, err := profile.Parse(file)
68
+	if err != nil {
69
+		t.Fatalf("%s: could not parse profile: %v", fname, err)
70
+	}
71
+
72
+	// Fix file names so they do not include absolute path names.
73
+	fix := func(s string) string {
74
+		const testdir = "/internal/report/"
75
+		pos := strings.Index(s, testdir)
76
+		if pos == -1 {
77
+			return s
78
+		}
79
+		return s[pos+len(testdir):]
80
+	}
81
+	for _, m := range p.Mapping {
82
+		m.File = fix(m.File)
83
+	}
84
+	for _, f := range p.Function {
85
+		f.Filename = fix(f.Filename)
86
+	}
87
+
88
+	return p
89
+}

+ 10
- 0
internal/report/testdata/README.md View File

1
+sample/ contains a sample program that can be profiled.
2
+sample.bin is its x86-64 binary.
3
+sample.cpu is a profile generated by sample.bin.
4
+
5
+To update the binary and profile:
6
+
7
+```shell
8
+go build -o sample.bin ./sample
9
+./sample.bin -cpuprofile sample.cpu
10
+```

BIN
internal/report/testdata/sample.bin View File


BIN
internal/report/testdata/sample.cpu View File


+ 41
- 0
internal/report/testdata/sample/sample.go View File

1
+// sample program that is used to produce some of the files in
2
+// pprof/internal/report/testdata.
3
+package main
4
+
5
+import (
6
+	"flag"
7
+	"fmt"
8
+	"log"
9
+	"math"
10
+	"os"
11
+	"runtime/pprof"
12
+)
13
+
14
+var cpuProfile = flag.String("cpuprofile", "", "where to write cpu profile")
15
+
16
+func main() {
17
+	flag.Parse()
18
+	f, err := os.Create(*cpuProfile)
19
+	if err != nil {
20
+		log.Fatal("could not create CPU profile: ", err)
21
+	}
22
+	if err := pprof.StartCPUProfile(f); err != nil {
23
+		log.Fatal("could not start CPU profile: ", err)
24
+	}
25
+	defer pprof.StopCPUProfile()
26
+	busyLoop()
27
+}
28
+
29
+func busyLoop() {
30
+	m := make(map[int]int)
31
+	for i := 0; i < 1000000; i++ {
32
+		m[i] = i + 10
33
+	}
34
+	var sum float64
35
+	for i := 0; i < 100; i++ {
36
+		for _, v := range m {
37
+			sum += math.Abs(float64(v))
38
+		}
39
+	}
40
+	fmt.Println("Sum", sum)
41
+}