Bladeren bron

Improve profile filter unit tests, fix show filtering of mappings (#354)

lvng 7 jaren geleden
bovenliggende
commit
616780541a
2 gewijzigde bestanden met toevoegingen van 375 en 34 verwijderingen
  1. 3
    0
      profile/filter.go
  2. 372
    34
      profile/filter_test.go

+ 3
- 0
profile/filter.go Bestand weergeven

@@ -142,6 +142,9 @@ func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line {
142 142
 // matchedLines returns the lines in the location that match
143 143
 // the regular expression.
144 144
 func (loc *Location) matchedLines(re *regexp.Regexp) []Line {
145
+	if m := loc.Mapping; m != nil && re.MatchString(m.File) {
146
+		return loc.Line
147
+	}
145 148
 	var lines []Line
146 149
 	for _, ln := range loc.Line {
147 150
 		if fn := ln.Function; fn != nil {

+ 372
- 34
profile/filter_test.go Bestand weergeven

@@ -1,4 +1,4 @@
1
-// Copyright 2014 Google Inc. All Rights Reserved.
1
+// Copyright 2018 Google Inc. All Rights Reserved.
2 2
 //
3 3
 // Licensed under the Apache License, Version 2.0 (the "License");
4 4
 // you may not use this file except in compliance with the License.
@@ -15,60 +15,398 @@
15 15
 package profile
16 16
 
17 17
 import (
18
+	"fmt"
18 19
 	"regexp"
20
+	"strings"
19 21
 	"testing"
22
+
23
+	"github.com/google/pprof/internal/proftest"
20 24
 )
21 25
 
22
-func TestFilter(t *testing.T) {
23
-	// Perform several forms of filtering on the test profile.
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
+}
24 30
 
25
-	type filterTestcase struct {
26
-		focus, ignore, hide, show *regexp.Regexp
27
-		fm, im, hm, hnm           bool
28
-	}
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
+}
29 44
 
30
-	for tx, tc := range []filterTestcase{
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
+func TestFilter(t *testing.T) {
105
+	for _, tc := range []struct {
106
+		// name is the name of the test case.
107
+		name string
108
+		// profile is the profile that gets filtered.
109
+		profile *Profile
110
+		// These are the inputs to FilterSamplesByName().
111
+		focus, ignore, hide, show *regexp.Regexp
112
+		// want{F,I,S,H}m are expected return values from FilterSamplesByName.
113
+		wantFm, wantIm, wantSm, wantHm bool
114
+		// wantSampleFuncs contains expected stack functions and sample value after
115
+		// filtering, in the same order as in the profile. The format is as
116
+		// returned by sampleFuncs function below, which is "callee caller: <num>".
117
+		wantSampleFuncs []string
118
+	}{
119
+		// No Filters
31 120
 		{
32
-			fm: true, // nil focus matches every sample
121
+			name:            "empty filters keep all frames",
122
+			profile:         noInlinesProfile,
123
+			wantFm:          true,
124
+			wantSampleFuncs: allNoInlinesSampleFuncs,
33 125
 		},
126
+		// Focus
34 127
 		{
35
-			focus: regexp.MustCompile("notfound"),
128
+			name:    "focus with no matches",
129
+			profile: noInlinesProfile,
130
+			focus:   regexp.MustCompile("unknown"),
36 131
 		},
37 132
 		{
38
-			ignore: regexp.MustCompile("foo.c"),
39
-			fm:     true,
40
-			im:     true,
133
+			name:    "focus matches function names",
134
+			profile: noInlinesProfile,
135
+			focus:   regexp.MustCompile("fun1"),
136
+			wantFm:  true,
137
+			wantSampleFuncs: []string{
138
+				"fun0 fun1 fun2 fun3: 1",
139
+				"fun4 fun5 fun1 fun6: 2",
140
+				"fun9 fun4 fun10 fun7: 4",
141
+			},
41 142
 		},
42 143
 		{
43
-			hide: regexp.MustCompile("lib.so"),
44
-			fm:   true,
45
-			hm:   true,
144
+			name:    "focus matches file names",
145
+			profile: noInlinesProfile,
146
+			focus:   regexp.MustCompile("file1"),
147
+			wantFm:  true,
148
+			wantSampleFuncs: []string{
149
+				"fun0 fun1 fun2 fun3: 1",
150
+				"fun4 fun5 fun1 fun6: 2",
151
+				"fun9 fun4 fun10 fun7: 4",
152
+			},
46 153
 		},
47 154
 		{
48
-			show: regexp.MustCompile("foo.c"),
49
-			fm:   true,
50
-			hnm:  true,
155
+			name:    "focus matches mapping names",
156
+			profile: noInlinesProfile,
157
+			focus:   regexp.MustCompile("map1"),
158
+			wantFm:  true,
159
+			wantSampleFuncs: []string{
160
+				"fun9 fun4 fun10 fun7: 4",
161
+			},
51 162
 		},
52 163
 		{
53
-			show: regexp.MustCompile("notfound"),
54
-			fm:   true,
164
+			name:    "focus matches inline functions",
165
+			profile: inlinesProfile,
166
+			focus:   regexp.MustCompile("fun5"),
167
+			wantFm:  true,
168
+			wantSampleFuncs: []string{
169
+				"fun4 fun5 fun6: 2",
170
+			},
171
+		},
172
+		// Ignore
173
+		{
174
+			name:            "ignore with no matches matches all samples",
175
+			profile:         noInlinesProfile,
176
+			ignore:          regexp.MustCompile("unknown"),
177
+			wantFm:          true,
178
+			wantSampleFuncs: allNoInlinesSampleFuncs,
179
+		},
180
+		{
181
+			name:    "ignore matches function names",
182
+			profile: noInlinesProfile,
183
+			ignore:  regexp.MustCompile("fun1"),
184
+			wantFm:  true,
185
+			wantIm:  true,
186
+			wantSampleFuncs: []string{
187
+				"fun7 fun8: 3",
188
+			},
189
+		},
190
+		{
191
+			name:    "ignore matches file names",
192
+			profile: noInlinesProfile,
193
+			ignore:  regexp.MustCompile("file1"),
194
+			wantFm:  true,
195
+			wantIm:  true,
196
+			wantSampleFuncs: []string{
197
+				"fun7 fun8: 3",
198
+			},
199
+		},
200
+		{
201
+			name:    "ignore matches mapping names",
202
+			profile: noInlinesProfile,
203
+			ignore:  regexp.MustCompile("map1"),
204
+			wantFm:  true,
205
+			wantIm:  true,
206
+			wantSampleFuncs: []string{
207
+				"fun0 fun1 fun2 fun3: 1",
208
+				"fun4 fun5 fun1 fun6: 2",
209
+				"fun7 fun8: 3",
210
+			},
211
+		},
212
+		{
213
+			name:    "ignore matches inline functions",
214
+			profile: inlinesProfile,
215
+			ignore:  regexp.MustCompile("fun5"),
216
+			wantFm:  true,
217
+			wantIm:  true,
218
+			wantSampleFuncs: []string{
219
+				"fun0 fun1 fun2 fun3: 1",
220
+			},
221
+		},
222
+		// Show
223
+		{
224
+			name:    "show with no matches",
225
+			profile: noInlinesProfile,
226
+			show:    regexp.MustCompile("unknown"),
227
+			wantFm:  true,
228
+		},
229
+		{
230
+			name:    "show matches function names",
231
+			profile: noInlinesProfile,
232
+			show:    regexp.MustCompile("fun1|fun2"),
233
+			wantFm:  true,
234
+			wantSm:  true,
235
+			wantSampleFuncs: []string{
236
+				"fun1 fun2: 1",
237
+				"fun1: 2",
238
+				"fun10: 4",
239
+			},
240
+		},
241
+		{
242
+			name:    "show matches file names",
243
+			profile: noInlinesProfile,
244
+			show:    regexp.MustCompile("file1|file3"),
245
+			wantFm:  true,
246
+			wantSm:  true,
247
+			wantSampleFuncs: []string{
248
+				"fun1 fun3: 1",
249
+				"fun1: 2",
250
+				"fun10: 4",
251
+			},
252
+		},
253
+		{
254
+			name:    "show matches mapping names",
255
+			profile: noInlinesProfile,
256
+			show:    regexp.MustCompile("map1"),
257
+			wantFm:  true,
258
+			wantSm:  true,
259
+			wantSampleFuncs: []string{
260
+				"fun10: 4",
261
+			},
262
+		},
263
+		{
264
+			name:    "show matches inline functions",
265
+			profile: inlinesProfile,
266
+			show:    regexp.MustCompile("fun[03]"),
267
+			wantFm:  true,
268
+			wantSm:  true,
269
+			wantSampleFuncs: []string{
270
+				"fun0 fun3: 1",
271
+			},
272
+		},
273
+		{
274
+			name:    "show keeps all lines when matching both mapping and function",
275
+			profile: inlinesProfile,
276
+			show:    regexp.MustCompile("map0|fun5"),
277
+			wantFm:  true,
278
+			wantSm:  true,
279
+			wantSampleFuncs: []string{
280
+				"fun0 fun1 fun2 fun3: 1",
281
+				"fun4 fun5 fun6: 2",
282
+			},
283
+		},
284
+		// Hide
285
+		{
286
+			name:            "hide with no matches",
287
+			profile:         noInlinesProfile,
288
+			hide:            regexp.MustCompile("unknown"),
289
+			wantFm:          true,
290
+			wantSampleFuncs: allNoInlinesSampleFuncs,
291
+		},
292
+		{
293
+			name:    "hide matches function names",
294
+			profile: noInlinesProfile,
295
+			hide:    regexp.MustCompile("fun1|fun2"),
296
+			wantFm:  true,
297
+			wantHm:  true,
298
+			wantSampleFuncs: []string{
299
+				"fun0 fun3: 1",
300
+				"fun4 fun5 fun6: 2",
301
+				"fun7 fun8: 3",
302
+				"fun9 fun4 fun7: 4",
303
+			},
304
+		},
305
+		{
306
+			name:    "hide matches file names",
307
+			profile: noInlinesProfile,
308
+			hide:    regexp.MustCompile("file1|file3"),
309
+			wantFm:  true,
310
+			wantHm:  true,
311
+			wantSampleFuncs: []string{
312
+				"fun0 fun2: 1",
313
+				"fun4 fun5 fun6: 2",
314
+				"fun7 fun8: 3",
315
+				"fun9 fun4 fun7: 4",
316
+			},
317
+		},
318
+		{
319
+			name:    "hide matches mapping names",
320
+			profile: noInlinesProfile,
321
+			hide:    regexp.MustCompile("map1"),
322
+			wantFm:  true,
323
+			wantHm:  true,
324
+			wantSampleFuncs: []string{
325
+				"fun0 fun1 fun2 fun3: 1",
326
+				"fun4 fun5 fun1 fun6: 2",
327
+				"fun7 fun8: 3",
328
+				"fun9 fun4 fun7: 4",
329
+			},
330
+		},
331
+		{
332
+			name:    "hide matches inline functions",
333
+			profile: inlinesProfile,
334
+			hide:    regexp.MustCompile("fun[125]"),
335
+			wantFm:  true,
336
+			wantHm:  true,
337
+			wantSampleFuncs: []string{
338
+				"fun0 fun3: 1",
339
+				"fun4 fun6: 2",
340
+			},
341
+		},
342
+		{
343
+			name:    "hide drops all lines when matching both mapping and function",
344
+			profile: inlinesProfile,
345
+			hide:    regexp.MustCompile("map0|fun5"),
346
+			wantFm:  true,
347
+			wantHm:  true,
348
+		},
349
+		// Compound filters
350
+		{
351
+			name:    "hides a stack matched by both focus and ignore",
352
+			profile: noInlinesProfile,
353
+			focus:   regexp.MustCompile("fun1|fun7"),
354
+			ignore:  regexp.MustCompile("fun1"),
355
+			wantFm:  true,
356
+			wantIm:  true,
357
+			wantSampleFuncs: []string{
358
+				"fun7 fun8: 3",
359
+			},
360
+		},
361
+		{
362
+			name:    "hides a function if both show and hide match it",
363
+			profile: noInlinesProfile,
364
+			show:    regexp.MustCompile("fun1"),
365
+			hide:    regexp.MustCompile("fun10"),
366
+			wantFm:  true,
367
+			wantSm:  true,
368
+			wantHm:  true,
369
+			wantSampleFuncs: []string{
370
+				"fun1: 1",
371
+				"fun1: 2",
372
+			},
55 373
 		},
56 374
 	} {
57
-		prof := *testProfile1.Copy()
58
-		gf, gi, gh, gnh := prof.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)
59
-		if gf != tc.fm {
60
-			t.Errorf("Filter #%d, got fm=%v, want %v", tx, gf, tc.fm)
61
-		}
62
-		if gi != tc.im {
63
-			t.Errorf("Filter #%d, got im=%v, want %v", tx, gi, tc.im)
64
-		}
65
-		if gh != tc.hm {
66
-			t.Errorf("Filter #%d, got hm=%v, want %v", tx, gh, tc.hm)
67
-		}
68
-		if gnh != tc.hnm {
69
-			t.Errorf("Filter #%d, got hnm=%v, want %v", tx, gnh, tc.hnm)
375
+		t.Run(tc.name, func(t *testing.T) {
376
+			p := tc.profile.Copy()
377
+			fm, im, hm, sm := p.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)
378
+
379
+			type match struct{ fm, im, hm, sm bool }
380
+			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 {
381
+				t.Errorf("match got %+v want %+v", got, want)
382
+			}
383
+
384
+			if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want {
385
+				diff, err := proftest.Diff([]byte(want), []byte(got))
386
+				if err != nil {
387
+					t.Fatalf("failed to get diff: %v", err)
388
+				}
389
+				t.Errorf("FilterSamplesByName: got diff(want->got):\n%s", diff)
390
+			}
391
+		})
392
+	}
393
+}
394
+
395
+// sampleFuncs returns a slice of strings where each string represents one
396
+// profile sample in the format "<fun1> <fun2> <fun3>: <value>". This allows
397
+// the expected values for test cases to be specifed in human-readable strings.
398
+func sampleFuncs(p *Profile) []string {
399
+	var ret []string
400
+	for _, s := range p.Sample {
401
+		var funcs []string
402
+		for _, loc := range s.Location {
403
+			for _, line := range loc.Line {
404
+				funcs = append(funcs, line.Function.Name)
405
+			}
70 406
 		}
407
+		ret = append(ret, fmt.Sprintf("%s: %d", strings.Join(funcs, " "), s.Value[0]))
71 408
 	}
409
+	return ret
72 410
 }
73 411
 
74 412
 func TestTagFilter(t *testing.T) {