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,17 +15,11 @@ h1 {
15 15
 .legend {
16 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 24
 .deadsrc {
31 25
 cursor: pointer;
@@ -70,41 +64,41 @@ Type: cpu<br>
70 64
 Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h1>line1000</h1>testdata/file1000.src
71 65
 <pre onClick="pprof_toggle_asm(event)">
72 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 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 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 79
 </pre>
86 80
 <h1>line3000</h1>testdata/file3000.src
87 81
 <pre onClick="pprof_toggle_asm(event)">
88 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 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 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 102
 </pre>
109 103
 
110 104
 </body>

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

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

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

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

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

@@ -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
+}

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

@@ -35,17 +35,11 @@ h1 {
35 35
 .legend {
36 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 44
 .deadsrc {
51 45
 cursor: pointer;

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

@@ -0,0 +1,89 @@
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

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,41 @@
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
+}