|
@@ -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) {
|