1 package driver
2
3 import (
4 "fmt"
5 "strings"
6 "testing"
7
8 "github.com/google/pprof/internal/proftest"
9 "github.com/google/pprof/profile"
10 )
11
12 const mainBinary = "/bin/main"
13
14 var cpuF = []*profile.Function{
15 {ID: 1, Name: "main", SystemName: "main", Filename: "main.c"},
16 {ID: 2, Name: "foo", SystemName: "foo", Filename: "foo.c"},
17 {ID: 3, Name: "foo_caller", SystemName: "foo_caller", Filename: "foo.c"},
18 {ID: 4, Name: "bar", SystemName: "bar", Filename: "bar.c"},
19 }
20
21 var cpuM = []*profile.Mapping{
22 {
23 ID: 1,
24 Start: 0x10000,
25 Limit: 0x40000,
26 File: mainBinary,
27 HasFunctions: true,
28 HasFilenames: true,
29 HasLineNumbers: true,
30 HasInlineFrames: true,
31 },
32 {
33 ID: 2,
34 Start: 0x1000,
35 Limit: 0x4000,
36 File: "/lib/lib.so",
37 HasFunctions: true,
38 HasFilenames: true,
39 HasLineNumbers: true,
40 HasInlineFrames: true,
41 },
42 }
43
44 var cpuL = []*profile.Location{
45 {
46 ID: 1000,
47 Mapping: cpuM[1],
48 Address: 0x1000,
49 Line: []profile.Line{
50 {Function: cpuF[0], Line: 1},
51 },
52 },
53 {
54 ID: 2000,
55 Mapping: cpuM[0],
56 Address: 0x2000,
57 Line: []profile.Line{
58 {Function: cpuF[1], Line: 2},
59 {Function: cpuF[2], Line: 1},
60 },
61 },
62 {
63 ID: 3000,
64 Mapping: cpuM[0],
65 Address: 0x3000,
66 Line: []profile.Line{
67 {Function: cpuF[1], Line: 2},
68 {Function: cpuF[2], Line: 1},
69 },
70 },
71 {
72 ID: 3001,
73 Mapping: cpuM[0],
74 Address: 0x3001,
75 Line: []profile.Line{
76 {Function: cpuF[2], Line: 2},
77 },
78 },
79 {
80 ID: 3002,
81 Mapping: cpuM[0],
82 Address: 0x3002,
83 Line: []profile.Line{
84 {Function: cpuF[2], Line: 3},
85 },
86 },
87 {
88 ID: 3003,
89 Mapping: cpuM[0],
90 Address: 0x3003,
91 Line: []profile.Line{
92 {Function: cpuF[3], Line: 1},
93 },
94 },
95 }
96
97 var testProfile1 = &profile.Profile{
98 TimeNanos: 10000,
99 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
100 Period: 1,
101 DurationNanos: 10e9,
102 SampleType: []*profile.ValueType{
103 {Type: "samples", Unit: "count"},
104 {Type: "cpu", Unit: "milliseconds"},
105 },
106 Sample: []*profile.Sample{
107 {
108 Location: []*profile.Location{cpuL[0]},
109 Value: []int64{1000, 1000},
110 Label: map[string][]string{
111 "key1": {"tag1"},
112 "key2": {"tag1"},
113 },
114 },
115 {
116 Location: []*profile.Location{cpuL[1], cpuL[0]},
117 Value: []int64{100, 100},
118 Label: map[string][]string{
119 "key1": {"tag2"},
120 "key3": {"tag2"},
121 },
122 },
123 {
124 Location: []*profile.Location{cpuL[2], cpuL[0]},
125 Value: []int64{10, 10},
126 Label: map[string][]string{
127 "key1": {"tag3"},
128 "key2": {"tag2"},
129 },
130 NumLabel: map[string][]int64{
131 "allocations": {1024},
132 },
133 NumUnit: map[string][]string{
134 "allocations": {""},
135 },
136 },
137 {
138 Location: []*profile.Location{cpuL[3], cpuL[0]},
139 Value: []int64{10000, 10000},
140 Label: map[string][]string{
141 "key1": {"tag4"},
142 "key2": {"tag1"},
143 },
144 NumLabel: map[string][]int64{
145 "allocations": {1024, 2048},
146 },
147 NumUnit: map[string][]string{
148 "allocations": {"bytes", "b"},
149 },
150 },
151 {
152 Location: []*profile.Location{cpuL[4], cpuL[0]},
153 Value: []int64{1, 1},
154 Label: map[string][]string{
155 "key1": {"tag4"},
156 "key2": {"tag1", "tag5"},
157 },
158 NumLabel: map[string][]int64{
159 "allocations": {1024, 1},
160 },
161 NumUnit: map[string][]string{
162 "allocations": {"byte", "kilobyte"},
163 },
164 },
165 {
166 Location: []*profile.Location{cpuL[5], cpuL[0]},
167 Value: []int64{200, 200},
168 NumLabel: map[string][]int64{
169 "allocations": {1024},
170 },
171 },
172 },
173 Location: cpuL,
174 Function: cpuF,
175 Mapping: cpuM,
176 }
177
178 func TestAddLabelNodesMatchBooleans(t *testing.T) {
179 type addLabelNodesTestcase struct {
180 name string
181 tagroot, tagleaf []string
182 outputUnit string
183 rootm, leafm bool
184
185
186
187 wantSampleFuncs []string
188 }
189 for _, tc := range []addLabelNodesTestcase{
190 {
191 name: "Without tagroot or tagleaf, add no extra nodes, and should not match",
192 wantSampleFuncs: []string{
193 "main(main.c) 1000",
194 "main(main.c);foo(foo.c);foo_caller(foo.c) 100",
195 "main(main.c);foo(foo.c);foo_caller(foo.c) 10",
196 "main(main.c);foo_caller(foo.c) 10000",
197 "main(main.c);foo_caller(foo.c) 1",
198 "main(main.c);bar(bar.c) 200",
199 },
200 },
201 {
202 name: "Keys that aren't found add empty nodes, and should not match",
203 tagroot: []string{"key404"},
204 tagleaf: []string{"key404"},
205 wantSampleFuncs: []string{
206 "(key404);main(main.c);(key404) 1000",
207 "(key404);main(main.c);foo(foo.c);foo_caller(foo.c);(key404) 100",
208 "(key404);main(main.c);foo(foo.c);foo_caller(foo.c);(key404) 10",
209 "(key404);main(main.c);foo_caller(foo.c);(key404) 10000",
210 "(key404);main(main.c);foo_caller(foo.c);(key404) 1",
211 "(key404);main(main.c);bar(bar.c);(key404) 200",
212 },
213 },
214 {
215 name: "tagroot adds nodes for key1 and reports a match",
216 tagroot: []string{"key1"},
217 rootm: true,
218 wantSampleFuncs: []string{
219 "tag1(key1);main(main.c) 1000",
220 "tag2(key1);main(main.c);foo(foo.c);foo_caller(foo.c) 100",
221 "tag3(key1);main(main.c);foo(foo.c);foo_caller(foo.c) 10",
222 "tag4(key1);main(main.c);foo_caller(foo.c) 10000",
223 "tag4(key1);main(main.c);foo_caller(foo.c) 1",
224 "(key1);main(main.c);bar(bar.c) 200",
225 },
226 },
227 {
228 name: "tagroot adds nodes for key2 and reports a match",
229 tagroot: []string{"key2"},
230 rootm: true,
231 wantSampleFuncs: []string{
232 "tag1(key2);main(main.c) 1000",
233 "(key2);main(main.c);foo(foo.c);foo_caller(foo.c) 100",
234 "tag2(key2);main(main.c);foo(foo.c);foo_caller(foo.c) 10",
235 "tag1(key2);main(main.c);foo_caller(foo.c) 10000",
236 "tag1,tag5(key2);main(main.c);foo_caller(foo.c) 1",
237 "(key2);main(main.c);bar(bar.c) 200",
238 },
239 },
240 {
241 name: "tagleaf adds nodes for key1 and reports a match",
242 tagleaf: []string{"key1"},
243 leafm: true,
244 wantSampleFuncs: []string{
245 "main(main.c);tag1(key1) 1000",
246 "main(main.c);foo(foo.c);foo_caller(foo.c);tag2(key1) 100",
247 "main(main.c);foo(foo.c);foo_caller(foo.c);tag3(key1) 10",
248 "main(main.c);foo_caller(foo.c);tag4(key1) 10000",
249 "main(main.c);foo_caller(foo.c);tag4(key1) 1",
250 "main(main.c);bar(bar.c);(key1) 200",
251 },
252 },
253 {
254 name: "tagleaf adds nodes for key3 and reports a match",
255 tagleaf: []string{"key3"},
256 leafm: true,
257 wantSampleFuncs: []string{
258 "main(main.c);(key3) 1000",
259 "main(main.c);foo(foo.c);foo_caller(foo.c);tag2(key3) 100",
260 "main(main.c);foo(foo.c);foo_caller(foo.c);(key3) 10",
261 "main(main.c);foo_caller(foo.c);(key3) 10000",
262 "main(main.c);foo_caller(foo.c);(key3) 1",
263 "main(main.c);bar(bar.c);(key3) 200",
264 },
265 },
266 {
267 name: "tagroot adds nodes for key1,key2 in order and reports a match",
268 tagroot: []string{"key1", "key2"},
269 rootm: true,
270 wantSampleFuncs: []string{
271 "tag1(key1);tag1(key2);main(main.c) 1000",
272 "tag2(key1);(key2);main(main.c);foo(foo.c);foo_caller(foo.c) 100",
273 "tag3(key1);tag2(key2);main(main.c);foo(foo.c);foo_caller(foo.c) 10",
274 "tag4(key1);tag1(key2);main(main.c);foo_caller(foo.c) 10000",
275 "tag4(key1);tag1,tag5(key2);main(main.c);foo_caller(foo.c) 1",
276 "(key1);(key2);main(main.c);bar(bar.c) 200",
277 },
278 },
279 {
280 name: "tagleaf adds nodes for key1,key2 in order and reports a match",
281 tagleaf: []string{"key1", "key2"},
282 leafm: true,
283 wantSampleFuncs: []string{
284 "main(main.c);tag1(key1);tag1(key2) 1000",
285 "main(main.c);foo(foo.c);foo_caller(foo.c);tag2(key1);(key2) 100",
286 "main(main.c);foo(foo.c);foo_caller(foo.c);tag3(key1);tag2(key2) 10",
287 "main(main.c);foo_caller(foo.c);tag4(key1);tag1(key2) 10000",
288 "main(main.c);foo_caller(foo.c);tag4(key1);tag1,tag5(key2) 1",
289 "main(main.c);bar(bar.c);(key1);(key2) 200",
290 },
291 },
292 {
293 name: "Numeric units are added with units with tagleaf",
294 tagleaf: []string{"allocations"},
295 leafm: true,
296 wantSampleFuncs: []string{
297 "main(main.c);(allocations) 1000",
298 "main(main.c);foo(foo.c);foo_caller(foo.c);(allocations) 100",
299 "main(main.c);foo(foo.c);foo_caller(foo.c);1024(allocations) 10",
300 "main(main.c);foo_caller(foo.c);1024B,2048B(allocations) 10000",
301 "main(main.c);foo_caller(foo.c);1024B,1024B(allocations) 1",
302 "main(main.c);bar(bar.c);1024(allocations) 200",
303 },
304 },
305 {
306 name: "Numeric units are added with units with tagroot",
307 tagroot: []string{"allocations"},
308 rootm: true,
309 wantSampleFuncs: []string{
310 "(allocations);main(main.c) 1000",
311 "(allocations);main(main.c);foo(foo.c);foo_caller(foo.c) 100",
312 "1024(allocations);main(main.c);foo(foo.c);foo_caller(foo.c) 10",
313 "1024B,2048B(allocations);main(main.c);foo_caller(foo.c) 10000",
314 "1024B,1024B(allocations);main(main.c);foo_caller(foo.c) 1",
315 "1024(allocations);main(main.c);bar(bar.c) 200",
316 },
317 },
318 {
319 name: "Numeric labels are formatted according to outputUnit",
320 outputUnit: "kB",
321 tagleaf: []string{"allocations"},
322 leafm: true,
323 wantSampleFuncs: []string{
324 "main(main.c);(allocations) 1000",
325 "main(main.c);foo(foo.c);foo_caller(foo.c);(allocations) 100",
326 "main(main.c);foo(foo.c);foo_caller(foo.c);1024(allocations) 10",
327 "main(main.c);foo_caller(foo.c);1kB,2kB(allocations) 10000",
328 "main(main.c);foo_caller(foo.c);1kB,1kB(allocations) 1",
329 "main(main.c);bar(bar.c);1024(allocations) 200",
330 },
331 },
332 {
333 name: "Numeric units with no units are handled properly by tagleaf",
334 tagleaf: []string{"allocations"},
335 leafm: true,
336 wantSampleFuncs: []string{
337 "main(main.c);(allocations) 1000",
338 "main(main.c);foo(foo.c);foo_caller(foo.c);(allocations) 100",
339 "main(main.c);foo(foo.c);foo_caller(foo.c);1024(allocations) 10",
340 "main(main.c);foo_caller(foo.c);1024B,2048B(allocations) 10000",
341 "main(main.c);foo_caller(foo.c);1024B,1024B(allocations) 1",
342 "main(main.c);bar(bar.c);1024(allocations) 200",
343 },
344 },
345 } {
346 tc := tc
347 t.Run(tc.name, func(t *testing.T) {
348 p := testProfile1.Copy()
349 rootm, leafm := addLabelNodes(p, tc.tagroot, tc.tagleaf, tc.outputUnit)
350 if rootm != tc.rootm {
351 t.Errorf("Got rootm=%v, want=%v", rootm, tc.rootm)
352 }
353 if leafm != tc.leafm {
354 t.Errorf("Got leafm=%v, want=%v", leafm, tc.leafm)
355 }
356 if got, want := strings.Join(stackCollapse(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want {
357 diff, err := proftest.Diff([]byte(want), []byte(got))
358 if err != nil {
359 t.Fatalf("Failed to get diff: %v", err)
360 }
361 t.Errorf("Profile samples got diff(want->got):\n%s", diff)
362 }
363 })
364 }
365 }
366
367
368
369
370
371
372 func stackCollapse(p *profile.Profile) []string {
373 var ret []string
374 for _, s := range p.Sample {
375 var funcs []string
376 for i := range s.Location {
377 loc := s.Location[len(s.Location)-1-i]
378 for _, line := range loc.Line {
379 funcs = append(funcs, fmt.Sprintf("%s(%s)", line.Function.Name, line.Function.Filename))
380 }
381 }
382 ret = append(ret, fmt.Sprintf("%s %d", strings.Join(funcs, ";"), s.Value[0]))
383 }
384 return ret
385 }
386
View as plain text