1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package report
16
17 import (
18 "bytes"
19 "os"
20 "path/filepath"
21 "regexp"
22 "runtime"
23 "strings"
24 "testing"
25
26 "github.com/google/pprof/internal/binutils"
27 "github.com/google/pprof/internal/graph"
28 "github.com/google/pprof/internal/proftest"
29 "github.com/google/pprof/profile"
30 )
31
32 type testcase struct {
33 rpt *Report
34 want string
35 }
36
37 func TestSource(t *testing.T) {
38 const path = "testdata/"
39
40 sampleValue1 := func(v []int64) int64 {
41 return v[1]
42 }
43
44 for _, tc := range []testcase{
45 {
46 rpt: New(
47 testProfile.Copy(),
48 &Options{
49 OutputFormat: List,
50 Symbol: regexp.MustCompile(`.`),
51 TrimPath: "/some/path",
52
53 SampleValue: sampleValue1,
54 SampleUnit: testProfile.SampleType[1].Unit,
55 },
56 ),
57 want: path + "source.rpt",
58 },
59 {
60 rpt: New(
61 testProfile.Copy(),
62 &Options{
63 OutputFormat: Dot,
64 CallTree: true,
65 Symbol: regexp.MustCompile(`.`),
66 TrimPath: "/some/path",
67
68 SampleValue: sampleValue1,
69 SampleUnit: testProfile.SampleType[1].Unit,
70 },
71 ),
72 want: path + "source.dot",
73 },
74 } {
75 var b bytes.Buffer
76 if err := Generate(&b, tc.rpt, &binutils.Binutils{}); err != nil {
77 t.Fatalf("%s: %v", tc.want, err)
78 }
79
80 gold, err := os.ReadFile(tc.want)
81 if err != nil {
82 t.Fatalf("%s: %v", tc.want, err)
83 }
84 if runtime.GOOS == "windows" {
85 if tc.rpt.options.OutputFormat == Dot {
86
87 gold = bytes.Replace(gold, []byte("testdata/"), []byte(`testdata\\`), -1)
88 } else {
89 gold = bytes.Replace(gold, []byte("testdata/"), []byte(`testdata\`), -1)
90 }
91 }
92 if string(b.String()) != string(gold) {
93 d, err := proftest.Diff(gold, b.Bytes())
94 if err != nil {
95 t.Fatalf("%s: %v", "source", err)
96 }
97 t.Error("source" + "\n" + string(d) + "\n" + "gold:\n" + tc.want)
98 }
99 }
100 }
101
102
103
104 func TestFilter(t *testing.T) {
105 const filter = "doesNotExist"
106
107 tests := []struct {
108 name string
109 format int
110 }{
111 {
112 name: "list",
113 format: List,
114 },
115 {
116 name: "disasm",
117 format: Dis,
118 },
119 {
120
121 name: "peek",
122 format: Tree,
123 },
124 }
125 for _, tc := range tests {
126 t.Run(tc.name, func(t *testing.T) {
127 rpt := New(testProfile.Copy(), &Options{
128 OutputFormat: tc.format,
129 Symbol: regexp.MustCompile(filter),
130 SampleValue: func(v []int64) int64 { return v[1] },
131 SampleUnit: testProfile.SampleType[1].Unit,
132 })
133
134 var buf bytes.Buffer
135 err := Generate(&buf, rpt, &binutils.Binutils{})
136 if err == nil {
137 t.Fatalf("Generate got nil, want error; buf = %s", buf.String())
138 }
139 if !strings.Contains(err.Error(), filter) {
140 t.Errorf("Error got %v, want it to contain %q", err, filter)
141 }
142 })
143 }
144 }
145
146
147 var testM = []*profile.Mapping{
148 {
149 ID: 1,
150 HasFunctions: true,
151 HasFilenames: true,
152 HasLineNumbers: true,
153 HasInlineFrames: true,
154 },
155 }
156
157
158 var testF = []*profile.Function{
159 {
160 ID: 1,
161 Name: "main",
162 Filename: "testdata/source1",
163 },
164 {
165 ID: 2,
166 Name: "foo",
167 Filename: "testdata/source1",
168 },
169 {
170 ID: 3,
171 Name: "bar",
172 Filename: "testdata/source1",
173 },
174 {
175 ID: 4,
176 Name: "tee",
177 Filename: "/some/path/testdata/source2",
178 },
179 }
180
181
182 var testL = []*profile.Location{
183 {
184 ID: 1,
185 Mapping: testM[0],
186 Line: []profile.Line{
187 {
188 Function: testF[0],
189 Line: 2,
190 Column: 2,
191 },
192 },
193 },
194 {
195 ID: 2,
196 Mapping: testM[0],
197 Line: []profile.Line{
198 {
199 Function: testF[1],
200 Line: 4,
201 Column: 4,
202 },
203 },
204 },
205 {
206 ID: 3,
207 Mapping: testM[0],
208 Line: []profile.Line{
209 {
210 Function: testF[2],
211 Line: 10,
212 },
213 },
214 },
215 {
216 ID: 4,
217 Mapping: testM[0],
218 Line: []profile.Line{
219 {
220 Function: testF[3],
221 Line: 2,
222 },
223 },
224 },
225 {
226 ID: 5,
227 Mapping: testM[0],
228 Line: []profile.Line{
229 {
230 Function: testF[3],
231 Line: 8,
232 },
233 },
234 },
235 {
236 ID: 6,
237 Mapping: testM[0],
238 Line: []profile.Line{
239 {
240 Function: testF[3],
241 Line: 7,
242 },
243 {
244 Function: testF[2],
245 Line: 6,
246 },
247 },
248 },
249 }
250
251
252
253 func testSample(value int64, locs ...*profile.Location) *profile.Sample {
254 return &profile.Sample{
255 Value: []int64{value},
256 Location: locs,
257 }
258 }
259
260
261
262 func makeTestProfile(samples ...*profile.Sample) *profile.Profile {
263 return &profile.Profile{
264 SampleType: []*profile.ValueType{{Type: "samples", Unit: "count"}},
265 Sample: samples,
266 Location: testL,
267 Function: testF,
268 Mapping: testM,
269 }
270 }
271
272
273
274 var testProfile = &profile.Profile{
275 PeriodType: &profile.ValueType{Type: "cpu", Unit: "millisecond"},
276 Period: 10,
277 DurationNanos: 10e9,
278 SampleType: []*profile.ValueType{
279 {Type: "samples", Unit: "count"},
280 {Type: "cpu", Unit: "cycles"},
281 },
282 Sample: []*profile.Sample{
283 {
284 Location: []*profile.Location{testL[0]},
285 Value: []int64{1, 1},
286 },
287 {
288 Location: []*profile.Location{testL[2], testL[1], testL[0]},
289 Value: []int64{1, 10},
290 },
291 {
292 Location: []*profile.Location{testL[4], testL[2], testL[0]},
293 Value: []int64{1, 100},
294 },
295 {
296 Location: []*profile.Location{testL[3], testL[0]},
297 Value: []int64{1, 1000},
298 },
299 {
300 Location: []*profile.Location{testL[4], testL[3], testL[0]},
301 Value: []int64{1, 10000},
302 },
303 },
304 Location: testL,
305 Function: testF,
306 Mapping: testM,
307 }
308
309 func TestDisambiguation(t *testing.T) {
310 parent1 := &graph.Node{Info: graph.NodeInfo{Name: "parent1"}}
311 parent2 := &graph.Node{Info: graph.NodeInfo{Name: "parent2"}}
312 child1 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent1}
313 child2 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent2}
314 child3 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent1}
315 sibling := &graph.Node{Info: graph.NodeInfo{Name: "sibling"}, Function: parent1}
316
317 n := []*graph.Node{parent1, parent2, child1, child2, child3, sibling}
318
319 wanted := map[*graph.Node]string{
320 parent1: "parent1",
321 parent2: "parent2",
322 child1: "child [1/2]",
323 child2: "child [2/2]",
324 child3: "child [1/2]",
325 sibling: "sibling",
326 }
327
328 g := &graph.Graph{Nodes: n}
329
330 names := getDisambiguatedNames(g)
331
332 for node, want := range wanted {
333 if got := names[node]; got != want {
334 t.Errorf("name %s, got %s, want %s", node.Info.Name, got, want)
335 }
336 }
337 }
338
339 func TestFunctionMap(t *testing.T) {
340
341 fm := make(functionMap)
342 nodes := []graph.NodeInfo{
343 {Name: "fun1"},
344 {Name: "fun2", File: "filename"},
345 {Name: "fun1"},
346 {Name: "fun2", File: "filename2"},
347 }
348
349 want := []struct {
350 wantFunction profile.Function
351 wantAdded bool
352 }{
353 {profile.Function{ID: 1, Name: "fun1"}, true},
354 {profile.Function{ID: 2, Name: "fun2", Filename: "filename"}, true},
355 {profile.Function{ID: 1, Name: "fun1"}, false},
356 {profile.Function{ID: 3, Name: "fun2", Filename: "filename2"}, true},
357 }
358
359 for i, tc := range nodes {
360 gotFunc, gotAdded := fm.findOrAdd(tc)
361 if got, want := gotFunc, want[i].wantFunction; *got != want {
362 t.Errorf("%d: got %v, want %v", i, got, want)
363 }
364 if got, want := gotAdded, want[i].wantAdded; got != want {
365 t.Errorf("%d: got %v, want %v", i, got, want)
366 }
367 }
368 }
369
370 func TestLegendActiveFilters(t *testing.T) {
371 activeFilterInput := []string{
372 "focus=123|456|789|101112|131415|161718|192021|222324|252627|282930|313233|343536|363738|acbdefghijklmnop",
373 "show=short filter",
374 }
375 expectedLegendActiveFilter := []string{
376 "Active filters:",
377 " focus=123|456|789|101112|131415|161718|192021|222324|252627|282930|313233|343536…",
378 " show=short filter",
379 }
380 legendActiveFilter := legendActiveFilters(activeFilterInput)
381 if len(legendActiveFilter) != len(expectedLegendActiveFilter) {
382 t.Errorf("wanted length %v got length %v", len(expectedLegendActiveFilter), len(legendActiveFilter))
383 }
384 for i := range legendActiveFilter {
385 if legendActiveFilter[i] != expectedLegendActiveFilter[i] {
386 t.Errorf("%d: want \"%v\", got \"%v\"", i, expectedLegendActiveFilter[i], legendActiveFilter[i])
387 }
388 }
389 }
390
391 func TestComputeTotal(t *testing.T) {
392 p1 := testProfile.Copy()
393 p1.Sample = []*profile.Sample{
394 {
395 Location: []*profile.Location{testL[0]},
396 Value: []int64{1, 1},
397 },
398 {
399 Location: []*profile.Location{testL[2], testL[1], testL[0]},
400 Value: []int64{1, 10},
401 },
402 {
403 Location: []*profile.Location{testL[4], testL[2], testL[0]},
404 Value: []int64{1, 100},
405 },
406 }
407
408 p2 := testProfile.Copy()
409 p2.Sample = []*profile.Sample{
410 {
411 Location: []*profile.Location{testL[0]},
412 Value: []int64{1, 1},
413 },
414 {
415 Location: []*profile.Location{testL[2], testL[1], testL[0]},
416 Value: []int64{1, -10},
417 },
418 {
419 Location: []*profile.Location{testL[4], testL[2], testL[0]},
420 Value: []int64{1, 100},
421 },
422 }
423
424 p3 := testProfile.Copy()
425 p3.Sample = []*profile.Sample{
426 {
427 Location: []*profile.Location{testL[0]},
428 Value: []int64{10000, 1},
429 },
430 {
431 Location: []*profile.Location{testL[2], testL[1], testL[0]},
432 Value: []int64{-10, 3},
433 Label: map[string][]string{"pprof::base": {"true"}},
434 },
435 {
436 Location: []*profile.Location{testL[2], testL[1], testL[0]},
437 Value: []int64{1000, -10},
438 },
439 {
440 Location: []*profile.Location{testL[2], testL[1], testL[0]},
441 Value: []int64{-9000, 3},
442 Label: map[string][]string{"pprof::base": {"true"}},
443 },
444 {
445 Location: []*profile.Location{testL[2], testL[1], testL[0]},
446 Value: []int64{-1, 3},
447 Label: map[string][]string{"pprof::base": {"true"}},
448 },
449 {
450 Location: []*profile.Location{testL[4], testL[2], testL[0]},
451 Value: []int64{100, 100},
452 },
453 {
454 Location: []*profile.Location{testL[2], testL[1], testL[0]},
455 Value: []int64{100, 3},
456 Label: map[string][]string{"pprof::base": {"true"}},
457 },
458 }
459
460 testcases := []struct {
461 desc string
462 prof *profile.Profile
463 value, meanDiv func(v []int64) int64
464 wantTotal int64
465 }{
466 {
467 desc: "no diff base, all positive values, index 1",
468 prof: p1,
469 value: func(v []int64) int64 {
470 return v[0]
471 },
472 wantTotal: 3,
473 },
474 {
475 desc: "no diff base, all positive values, index 2",
476 prof: p1,
477 value: func(v []int64) int64 {
478 return v[1]
479 },
480 wantTotal: 111,
481 },
482 {
483 desc: "no diff base, some negative values",
484 prof: p2,
485 value: func(v []int64) int64 {
486 return v[1]
487 },
488 wantTotal: 111,
489 },
490 {
491 desc: "diff base, some negative values",
492 prof: p3,
493 value: func(v []int64) int64 {
494 return v[0]
495 },
496 wantTotal: 9111,
497 },
498 }
499
500 for _, tc := range testcases {
501 t.Run(tc.desc, func(t *testing.T) {
502 if gotTotal := computeTotal(tc.prof, tc.value, tc.meanDiv); gotTotal != tc.wantTotal {
503 t.Errorf("got total %d, want %v", gotTotal, tc.wantTotal)
504 }
505 })
506 }
507 }
508
509 func TestPrintAssemblyErrorMessage(t *testing.T) {
510 profile := readProfile(filepath.Join("testdata", "sample.cpu"), t)
511
512 for _, tc := range []struct {
513 desc string
514 symbol string
515 want string
516 }{
517 {
518 desc: "no matched symbol in binary",
519 symbol: "symbol-not-exist",
520 want: "no matches found for regexp symbol-not-exist",
521 },
522 {
523 desc: "no matched address in binary",
524 symbol: "0xffffaaaa",
525 want: "no matches found for address 0xffffaaaa",
526 },
527 {
528 desc: "matched address in binary but not in the profile",
529 symbol: "0x400000",
530 want: "address 0x400000 found in binary, but the corresponding symbols do not have samples in the profile",
531 },
532 } {
533 rpt := New(
534 profile.Copy(),
535 &Options{
536 OutputFormat: List,
537 Symbol: regexp.MustCompile(tc.symbol),
538 SampleValue: func(v []int64) int64 {
539 return v[1]
540 },
541 SampleUnit: profile.SampleType[1].Unit,
542 },
543 )
544
545 if err := PrintAssembly(os.Stdout, rpt, &binutils.Binutils{}, -1); err == nil || err.Error() != tc.want {
546 t.Errorf(`Got "%v", want %q`, err, tc.want)
547 }
548 }
549 }
550
View as plain text