1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package profile
16
17 import (
18 "fmt"
19 "regexp"
20 "strings"
21 "testing"
22
23 "github.com/google/pprof/internal/proftest"
24 )
25
26 var mappings = []*Mapping{
27 {ID: 1, Start: 0x10000, Limit: 0x40000, File: "map0", HasFunctions: true, HasFilenames: true, HasLineNumbers: true, HasInlineFrames: true},
28 {ID: 2, Start: 0x50000, Limit: 0x70000, File: "map1", HasFunctions: true, HasFilenames: true, HasLineNumbers: true, HasInlineFrames: true},
29 }
30
31 var functions = []*Function{
32 {ID: 1, Name: "fun0", SystemName: "fun0", Filename: "file0"},
33 {ID: 2, Name: "fun1", SystemName: "fun1", Filename: "file1"},
34 {ID: 3, Name: "fun2", SystemName: "fun2", Filename: "file2"},
35 {ID: 4, Name: "fun3", SystemName: "fun3", Filename: "file3"},
36 {ID: 5, Name: "fun4", SystemName: "fun4", Filename: "file4"},
37 {ID: 6, Name: "fun5", SystemName: "fun5", Filename: "file5"},
38 {ID: 7, Name: "fun6", SystemName: "fun6", Filename: "file6"},
39 {ID: 8, Name: "fun7", SystemName: "fun7", Filename: "file7"},
40 {ID: 9, Name: "fun8", SystemName: "fun8", Filename: "file8"},
41 {ID: 10, Name: "fun9", SystemName: "fun9", Filename: "file9"},
42 {ID: 11, Name: "fun10", SystemName: "fun10", Filename: "file10"},
43 }
44
45 var noInlinesLocs = []*Location{
46 {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}}},
47 {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{{Function: functions[1], Line: 1}}},
48 {ID: 3, Mapping: mappings[0], Address: 0x3000, Line: []Line{{Function: functions[2], Line: 1}}},
49 {ID: 4, Mapping: mappings[0], Address: 0x4000, Line: []Line{{Function: functions[3], Line: 1}}},
50 {ID: 5, Mapping: mappings[0], Address: 0x5000, Line: []Line{{Function: functions[4], Line: 1}}},
51 {ID: 6, Mapping: mappings[0], Address: 0x6000, Line: []Line{{Function: functions[5], Line: 1}}},
52 {ID: 7, Mapping: mappings[0], Address: 0x7000, Line: []Line{{Function: functions[6], Line: 1}}},
53 {ID: 8, Mapping: mappings[0], Address: 0x8000, Line: []Line{{Function: functions[7], Line: 1}}},
54 {ID: 9, Mapping: mappings[0], Address: 0x9000, Line: []Line{{Function: functions[8], Line: 1}}},
55 {ID: 10, Mapping: mappings[0], Address: 0x10000, Line: []Line{{Function: functions[9], Line: 1}}},
56 {ID: 11, Mapping: mappings[1], Address: 0x11000, Line: []Line{{Function: functions[10], Line: 1}}},
57 }
58
59 var noInlinesProfile = &Profile{
60 TimeNanos: 10000,
61 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
62 Period: 1,
63 DurationNanos: 10e9,
64 SampleType: []*ValueType{{Type: "samples", Unit: "count"}},
65 Mapping: mappings,
66 Function: functions,
67 Location: noInlinesLocs,
68 Sample: []*Sample{
69 {Value: []int64{1}, Location: []*Location{noInlinesLocs[0], noInlinesLocs[1], noInlinesLocs[2], noInlinesLocs[3]}},
70 {Value: []int64{2}, Location: []*Location{noInlinesLocs[4], noInlinesLocs[5], noInlinesLocs[1], noInlinesLocs[6]}},
71 {Value: []int64{3}, Location: []*Location{noInlinesLocs[7], noInlinesLocs[8]}},
72 {Value: []int64{4}, Location: []*Location{noInlinesLocs[9], noInlinesLocs[4], noInlinesLocs[10], noInlinesLocs[7]}},
73 },
74 }
75
76 var allNoInlinesSampleFuncs = []string{
77 "fun0 fun1 fun2 fun3: 1",
78 "fun4 fun5 fun1 fun6: 2",
79 "fun7 fun8: 3",
80 "fun9 fun4 fun10 fun7: 4",
81 }
82
83 var inlinesLocs = []*Location{
84 {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}},
85 {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{{Function: functions[2], Line: 1}, {Function: functions[3], Line: 1}}},
86 {ID: 3, Mapping: mappings[0], Address: 0x3000, Line: []Line{{Function: functions[4], Line: 1}, {Function: functions[5], Line: 1}, {Function: functions[6], Line: 1}}},
87 }
88
89 var inlinesProfile = &Profile{
90 TimeNanos: 10000,
91 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
92 Period: 1,
93 DurationNanos: 10e9,
94 SampleType: []*ValueType{{Type: "samples", Unit: "count"}},
95 Mapping: mappings,
96 Function: functions,
97 Location: inlinesLocs,
98 Sample: []*Sample{
99 {Value: []int64{1}, Location: []*Location{inlinesLocs[0], inlinesLocs[1]}},
100 {Value: []int64{2}, Location: []*Location{inlinesLocs[2]}},
101 },
102 }
103
104 var emptyLinesLocs = []*Location{
105 {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}},
106 {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{}},
107 {ID: 3, Mapping: mappings[1], Address: 0x2000, Line: []Line{}},
108 }
109
110 var emptyLinesProfile = &Profile{
111 TimeNanos: 10000,
112 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
113 Period: 1,
114 DurationNanos: 10e9,
115 SampleType: []*ValueType{{Type: "samples", Unit: "count"}},
116 Mapping: mappings,
117 Function: functions,
118 Location: emptyLinesLocs,
119 Sample: []*Sample{
120 {Value: []int64{1}, Location: []*Location{emptyLinesLocs[0], emptyLinesLocs[1]}},
121 {Value: []int64{2}, Location: []*Location{emptyLinesLocs[2]}},
122 {Value: []int64{3}, Location: []*Location{}},
123 },
124 }
125
126 func TestFilterSamplesByName(t *testing.T) {
127 for _, tc := range []struct {
128
129 name string
130
131 profile *Profile
132
133 focus, ignore, hide, show *regexp.Regexp
134
135 wantFm, wantIm, wantSm, wantHm bool
136
137
138
139 wantSampleFuncs []string
140 }{
141
142 {
143 name: "empty filters keep all frames",
144 profile: noInlinesProfile,
145 wantFm: true,
146 wantSampleFuncs: allNoInlinesSampleFuncs,
147 },
148
149 {
150 name: "focus with no matches",
151 profile: noInlinesProfile,
152 focus: regexp.MustCompile("unknown"),
153 },
154 {
155 name: "focus matches function names",
156 profile: noInlinesProfile,
157 focus: regexp.MustCompile("fun1"),
158 wantFm: true,
159 wantSampleFuncs: []string{
160 "fun0 fun1 fun2 fun3: 1",
161 "fun4 fun5 fun1 fun6: 2",
162 "fun9 fun4 fun10 fun7: 4",
163 },
164 },
165 {
166 name: "focus matches file names",
167 profile: noInlinesProfile,
168 focus: regexp.MustCompile("file1"),
169 wantFm: true,
170 wantSampleFuncs: []string{
171 "fun0 fun1 fun2 fun3: 1",
172 "fun4 fun5 fun1 fun6: 2",
173 "fun9 fun4 fun10 fun7: 4",
174 },
175 },
176 {
177 name: "focus matches mapping names",
178 profile: noInlinesProfile,
179 focus: regexp.MustCompile("map1"),
180 wantFm: true,
181 wantSampleFuncs: []string{
182 "fun9 fun4 fun10 fun7: 4",
183 },
184 },
185 {
186 name: "focus matches inline functions",
187 profile: inlinesProfile,
188 focus: regexp.MustCompile("fun5"),
189 wantFm: true,
190 wantSampleFuncs: []string{
191 "fun4 fun5 fun6: 2",
192 },
193 },
194
195 {
196 name: "ignore with no matches matches all samples",
197 profile: noInlinesProfile,
198 ignore: regexp.MustCompile("unknown"),
199 wantFm: true,
200 wantSampleFuncs: allNoInlinesSampleFuncs,
201 },
202 {
203 name: "ignore matches function names",
204 profile: noInlinesProfile,
205 ignore: regexp.MustCompile("fun1"),
206 wantFm: true,
207 wantIm: true,
208 wantSampleFuncs: []string{
209 "fun7 fun8: 3",
210 },
211 },
212 {
213 name: "ignore matches file names",
214 profile: noInlinesProfile,
215 ignore: regexp.MustCompile("file1"),
216 wantFm: true,
217 wantIm: true,
218 wantSampleFuncs: []string{
219 "fun7 fun8: 3",
220 },
221 },
222 {
223 name: "ignore matches mapping names",
224 profile: noInlinesProfile,
225 ignore: regexp.MustCompile("map1"),
226 wantFm: true,
227 wantIm: true,
228 wantSampleFuncs: []string{
229 "fun0 fun1 fun2 fun3: 1",
230 "fun4 fun5 fun1 fun6: 2",
231 "fun7 fun8: 3",
232 },
233 },
234 {
235 name: "ignore matches inline functions",
236 profile: inlinesProfile,
237 ignore: regexp.MustCompile("fun5"),
238 wantFm: true,
239 wantIm: true,
240 wantSampleFuncs: []string{
241 "fun0 fun1 fun2 fun3: 1",
242 },
243 },
244
245 {
246 name: "show with no matches",
247 profile: noInlinesProfile,
248 show: regexp.MustCompile("unknown"),
249 wantFm: true,
250 },
251 {
252 name: "show matches function names",
253 profile: noInlinesProfile,
254 show: regexp.MustCompile("fun1|fun2"),
255 wantFm: true,
256 wantSm: true,
257 wantSampleFuncs: []string{
258 "fun1 fun2: 1",
259 "fun1: 2",
260 "fun10: 4",
261 },
262 },
263 {
264 name: "show matches file names",
265 profile: noInlinesProfile,
266 show: regexp.MustCompile("file1|file3"),
267 wantFm: true,
268 wantSm: true,
269 wantSampleFuncs: []string{
270 "fun1 fun3: 1",
271 "fun1: 2",
272 "fun10: 4",
273 },
274 },
275 {
276 name: "show matches mapping names",
277 profile: noInlinesProfile,
278 show: regexp.MustCompile("map1"),
279 wantFm: true,
280 wantSm: true,
281 wantSampleFuncs: []string{
282 "fun10: 4",
283 },
284 },
285 {
286 name: "show matches inline functions",
287 profile: inlinesProfile,
288 show: regexp.MustCompile("fun[03]"),
289 wantFm: true,
290 wantSm: true,
291 wantSampleFuncs: []string{
292 "fun0 fun3: 1",
293 },
294 },
295 {
296 name: "show keeps all lines when matching both mapping and function",
297 profile: inlinesProfile,
298 show: regexp.MustCompile("map0|fun5"),
299 wantFm: true,
300 wantSm: true,
301 wantSampleFuncs: []string{
302 "fun0 fun1 fun2 fun3: 1",
303 "fun4 fun5 fun6: 2",
304 },
305 },
306
307 {
308 name: "hide with no matches",
309 profile: noInlinesProfile,
310 hide: regexp.MustCompile("unknown"),
311 wantFm: true,
312 wantSampleFuncs: allNoInlinesSampleFuncs,
313 },
314 {
315 name: "hide matches function names",
316 profile: noInlinesProfile,
317 hide: regexp.MustCompile("fun1|fun2"),
318 wantFm: true,
319 wantHm: true,
320 wantSampleFuncs: []string{
321 "fun0 fun3: 1",
322 "fun4 fun5 fun6: 2",
323 "fun7 fun8: 3",
324 "fun9 fun4 fun7: 4",
325 },
326 },
327 {
328 name: "hide matches file names",
329 profile: noInlinesProfile,
330 hide: regexp.MustCompile("file1|file3"),
331 wantFm: true,
332 wantHm: true,
333 wantSampleFuncs: []string{
334 "fun0 fun2: 1",
335 "fun4 fun5 fun6: 2",
336 "fun7 fun8: 3",
337 "fun9 fun4 fun7: 4",
338 },
339 },
340 {
341 name: "hide matches mapping names",
342 profile: noInlinesProfile,
343 hide: regexp.MustCompile("map1"),
344 wantFm: true,
345 wantHm: true,
346 wantSampleFuncs: []string{
347 "fun0 fun1 fun2 fun3: 1",
348 "fun4 fun5 fun1 fun6: 2",
349 "fun7 fun8: 3",
350 "fun9 fun4 fun7: 4",
351 },
352 },
353 {
354 name: "hide matches inline functions",
355 profile: inlinesProfile,
356 hide: regexp.MustCompile("fun[125]"),
357 wantFm: true,
358 wantHm: true,
359 wantSampleFuncs: []string{
360 "fun0 fun3: 1",
361 "fun4 fun6: 2",
362 },
363 },
364 {
365 name: "hide drops all lines when matching both mapping and function",
366 profile: inlinesProfile,
367 hide: regexp.MustCompile("map0|fun5"),
368 wantFm: true,
369 wantHm: true,
370 },
371
372 {
373 name: "hides a stack matched by both focus and ignore",
374 profile: noInlinesProfile,
375 focus: regexp.MustCompile("fun1|fun7"),
376 ignore: regexp.MustCompile("fun1"),
377 wantFm: true,
378 wantIm: true,
379 wantSampleFuncs: []string{
380 "fun7 fun8: 3",
381 },
382 },
383 {
384 name: "hides a function if both show and hide match it",
385 profile: noInlinesProfile,
386 show: regexp.MustCompile("fun1"),
387 hide: regexp.MustCompile("fun10"),
388 wantFm: true,
389 wantSm: true,
390 wantHm: true,
391 wantSampleFuncs: []string{
392 "fun1: 1",
393 "fun1: 2",
394 },
395 },
396 } {
397 t.Run(tc.name, func(t *testing.T) {
398 p := tc.profile.Copy()
399 fm, im, hm, sm := p.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)
400
401 type match struct{ fm, im, hm, sm bool }
402 if got, want := (match{fm: fm, im: im, hm: hm, sm: sm}), (match{fm: tc.wantFm, im: tc.wantIm, hm: tc.wantHm, sm: tc.wantSm}); got != want {
403 t.Errorf("match got %+v want %+v", got, want)
404 }
405
406 if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want {
407 diff, err := proftest.Diff([]byte(want), []byte(got))
408 if err != nil {
409 t.Fatalf("failed to get diff: %v", err)
410 }
411 t.Errorf("FilterSamplesByName: got diff(want->got):\n%s", diff)
412 }
413 })
414 }
415 }
416
417 func TestShowFrom(t *testing.T) {
418 for _, tc := range []struct {
419 name string
420 profile *Profile
421 showFrom *regexp.Regexp
422
423 wantMatch bool
424
425
426
427 wantSampleFuncs []string
428 }{
429 {
430 name: "nil showFrom keeps all frames",
431 profile: noInlinesProfile,
432 wantMatch: false,
433 wantSampleFuncs: allNoInlinesSampleFuncs,
434 },
435 {
436 name: "showFrom with no matches drops all samples",
437 profile: noInlinesProfile,
438 showFrom: regexp.MustCompile("unknown"),
439 wantMatch: false,
440 },
441 {
442 name: "showFrom matches function names",
443 profile: noInlinesProfile,
444 showFrom: regexp.MustCompile("fun1"),
445 wantMatch: true,
446 wantSampleFuncs: []string{
447 "fun0 fun1: 1",
448 "fun4 fun5 fun1: 2",
449 "fun9 fun4 fun10: 4",
450 },
451 },
452 {
453 name: "showFrom matches file names",
454 profile: noInlinesProfile,
455 showFrom: regexp.MustCompile("file1"),
456 wantMatch: true,
457 wantSampleFuncs: []string{
458 "fun0 fun1: 1",
459 "fun4 fun5 fun1: 2",
460 "fun9 fun4 fun10: 4",
461 },
462 },
463 {
464 name: "showFrom matches mapping names",
465 profile: noInlinesProfile,
466 showFrom: regexp.MustCompile("map1"),
467 wantMatch: true,
468 wantSampleFuncs: []string{
469 "fun9 fun4 fun10: 4",
470 },
471 },
472 {
473 name: "showFrom drops frames above highest of multiple matches",
474 profile: noInlinesProfile,
475 showFrom: regexp.MustCompile("fun[12]"),
476 wantMatch: true,
477 wantSampleFuncs: []string{
478 "fun0 fun1 fun2: 1",
479 "fun4 fun5 fun1: 2",
480 "fun9 fun4 fun10: 4",
481 },
482 },
483 {
484 name: "showFrom matches inline functions",
485 profile: inlinesProfile,
486 showFrom: regexp.MustCompile("fun0|fun5"),
487 wantMatch: true,
488 wantSampleFuncs: []string{
489 "fun0: 1",
490 "fun4 fun5: 2",
491 },
492 },
493 {
494 name: "showFrom drops frames above highest of multiple inline matches",
495 profile: inlinesProfile,
496 showFrom: regexp.MustCompile("fun[1245]"),
497 wantMatch: true,
498 wantSampleFuncs: []string{
499 "fun0 fun1 fun2: 1",
500 "fun4 fun5: 2",
501 },
502 },
503 {
504 name: "showFrom keeps all lines when matching mapping and function",
505 profile: inlinesProfile,
506 showFrom: regexp.MustCompile("map0|fun5"),
507 wantMatch: true,
508 wantSampleFuncs: []string{
509 "fun0 fun1 fun2 fun3: 1",
510 "fun4 fun5 fun6: 2",
511 },
512 },
513 {
514 name: "showFrom matches location with empty lines",
515 profile: emptyLinesProfile,
516 showFrom: regexp.MustCompile("map1"),
517 wantMatch: true,
518 wantSampleFuncs: []string{
519 ": 2",
520 },
521 },
522 } {
523 t.Run(tc.name, func(t *testing.T) {
524 p := tc.profile.Copy()
525
526 if gotMatch := p.ShowFrom(tc.showFrom); gotMatch != tc.wantMatch {
527 t.Errorf("match got %+v, want %+v", gotMatch, tc.wantMatch)
528 }
529
530 if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want {
531 diff, err := proftest.Diff([]byte(want), []byte(got))
532 if err != nil {
533 t.Fatalf("failed to get diff: %v", err)
534 }
535 t.Errorf("profile samples got diff(want->got):\n%s", diff)
536 }
537 })
538 }
539 }
540
541
542
543
544
545 func sampleFuncs(p *Profile) []string {
546 var ret []string
547 for _, s := range p.Sample {
548 var funcs []string
549 for _, loc := range s.Location {
550 for _, line := range loc.Line {
551 funcs = append(funcs, line.Function.Name)
552 }
553 }
554 ret = append(ret, fmt.Sprintf("%s: %d", strings.Join(funcs, " "), s.Value[0]))
555 }
556 return ret
557 }
558
559 func TestTagFilter(t *testing.T) {
560
561
562 type filterTestcase struct {
563 include, exclude *regexp.Regexp
564 im, em bool
565 count int
566 }
567
568 countTags := func(p *Profile) map[string]bool {
569 tags := make(map[string]bool)
570
571 for _, s := range p.Sample {
572 for l := range s.Label {
573 tags[l] = true
574 }
575 for l := range s.NumLabel {
576 tags[l] = true
577 }
578 }
579 return tags
580 }
581
582 for tx, tc := range []filterTestcase{
583 {nil, nil, true, false, 3},
584 {regexp.MustCompile("notfound"), nil, false, false, 0},
585 {regexp.MustCompile("key1"), nil, true, false, 1},
586 {nil, regexp.MustCompile("key[12]"), true, true, 1},
587 } {
588 prof := testProfile1.Copy()
589 gim, gem := prof.FilterTagsByName(tc.include, tc.exclude)
590 if gim != tc.im {
591 t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im)
592 }
593 if gem != tc.em {
594 t.Errorf("Filter #%d, got exclude match=%v, want %v", tx, gem, tc.em)
595 }
596 if tags := countTags(prof); len(tags) != tc.count {
597 t.Errorf("Filter #%d, got %d tags[%v], want %d", tx, len(tags), tags, tc.count)
598 }
599 }
600 }
601
View as plain text