1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package graph
16
17 import (
18 "fmt"
19 "testing"
20
21 "github.com/google/pprof/profile"
22 )
23
24 func edgeDebugString(edge *Edge) string {
25 debug := ""
26 debug += fmt.Sprintf("\t\tSrc: %p\n", edge.Src)
27 debug += fmt.Sprintf("\t\tDest: %p\n", edge.Dest)
28 debug += fmt.Sprintf("\t\tWeight: %d\n", edge.Weight)
29 debug += fmt.Sprintf("\t\tResidual: %t\n", edge.Residual)
30 debug += fmt.Sprintf("\t\tInline: %t\n", edge.Inline)
31 return debug
32 }
33
34 func edgeMapsDebugString(in, out EdgeMap) string {
35 debug := ""
36 debug += "In Edges:\n"
37 for parent, edge := range in {
38 debug += fmt.Sprintf("\tParent: %p\n", parent)
39 debug += edgeDebugString(edge)
40 }
41 debug += "Out Edges:\n"
42 for child, edge := range out {
43 debug += fmt.Sprintf("\tChild: %p\n", child)
44 debug += edgeDebugString(edge)
45 }
46 return debug
47 }
48
49 func graphDebugString(graph *Graph) string {
50 debug := ""
51 for i, node := range graph.Nodes {
52 debug += fmt.Sprintf("Node %d: %p\n", i, node)
53 }
54
55 for i, node := range graph.Nodes {
56 debug += "\n"
57 debug += fmt.Sprintf("=== Node %d: %p ===\n", i, node)
58 debug += edgeMapsDebugString(node.In, node.Out)
59 }
60 return debug
61 }
62
63 func expectedNodesDebugString(expected []expectedNode) string {
64 debug := ""
65 for i, node := range expected {
66 debug += fmt.Sprintf("Node %d: %p\n", i, node.node)
67 }
68
69 for i, node := range expected {
70 debug += "\n"
71 debug += fmt.Sprintf("=== Node %d: %p ===\n", i, node.node)
72 debug += edgeMapsDebugString(node.in, node.out)
73 }
74 return debug
75 }
76
77
78 func edgeMapsEqual(this, that EdgeMap) bool {
79 if len(this) != len(that) {
80 return false
81 }
82 for node, thisEdge := range this {
83 if *thisEdge != *that[node] {
84 return false
85 }
86 }
87 return true
88 }
89
90
91 func nodesEqual(node *Node, expected expectedNode) bool {
92 return node == expected.node && edgeMapsEqual(node.In, expected.in) &&
93 edgeMapsEqual(node.Out, expected.out)
94 }
95
96
97 func graphsEqual(graph *Graph, expected []expectedNode) bool {
98 if len(graph.Nodes) != len(expected) {
99 return false
100 }
101 expectedSet := make(map[*Node]expectedNode)
102 for i := range expected {
103 expectedSet[expected[i].node] = expected[i]
104 }
105
106 for _, node := range graph.Nodes {
107 expectedNode, found := expectedSet[node]
108 if !found || !nodesEqual(node, expectedNode) {
109 return false
110 }
111 }
112 return true
113 }
114
115 type expectedNode struct {
116 node *Node
117 in, out EdgeMap
118 }
119
120 type trimTreeTestcase struct {
121 initial *Graph
122 expected []expectedNode
123 keep NodePtrSet
124 }
125
126
127 func makeExpectedEdgeResidual(parent, child expectedNode) {
128 parent.out[child.node].Residual = true
129 child.in[parent.node].Residual = true
130 }
131
132 func makeEdgeInline(edgeMap EdgeMap, node *Node) {
133 edgeMap[node].Inline = true
134 }
135
136 func setEdgeWeight(edgeMap EdgeMap, node *Node, weight int64) {
137 edgeMap[node].Weight = weight
138 }
139
140
141 func createEdges(parent *Node, children ...*Node) {
142 for _, child := range children {
143 edge := &Edge{
144 Src: parent,
145 Dest: child,
146 }
147 parent.Out[child] = edge
148 child.In[parent] = edge
149 }
150 }
151
152
153 func createEmptyNode() *Node {
154 return &Node{
155 In: make(EdgeMap),
156 Out: make(EdgeMap),
157 }
158 }
159
160
161 func createExpectedNodes(nodes ...*Node) ([]expectedNode, NodePtrSet) {
162 expected := make([]expectedNode, len(nodes))
163 keep := make(NodePtrSet, len(nodes))
164
165 for i, node := range nodes {
166 expected[i] = expectedNode{
167 node: node,
168 in: make(EdgeMap),
169 out: make(EdgeMap),
170 }
171 keep[node] = true
172 }
173
174 return expected, keep
175 }
176
177
178
179 func createExpectedEdges(parent expectedNode, children ...expectedNode) {
180 for _, child := range children {
181 edge := &Edge{
182 Src: parent.node,
183 Dest: child.node,
184 }
185 parent.out[child.node] = edge
186 child.in[parent.node] = edge
187 }
188 }
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 func createTestCase1() trimTreeTestcase {
204
205 graph := &Graph{make(Nodes, 4)}
206 nodes := graph.Nodes
207 for i := range nodes {
208 nodes[i] = createEmptyNode()
209 }
210 createEdges(nodes[0], nodes[1])
211 createEdges(nodes[1], nodes[2], nodes[3])
212 makeEdgeInline(nodes[0].Out, nodes[1])
213 makeEdgeInline(nodes[1].Out, nodes[2])
214 setEdgeWeight(nodes[0].Out, nodes[1], 5)
215 setEdgeWeight(nodes[1].Out, nodes[2], 3)
216 setEdgeWeight(nodes[1].Out, nodes[3], 4)
217
218
219 expected, keep := createExpectedNodes(nodes[0], nodes[2], nodes[3])
220 createExpectedEdges(expected[0], expected[1], expected[2])
221 makeEdgeInline(expected[0].out, expected[1].node)
222 makeExpectedEdgeResidual(expected[0], expected[1])
223 makeExpectedEdgeResidual(expected[0], expected[2])
224 setEdgeWeight(expected[0].out, expected[1].node, 3)
225 setEdgeWeight(expected[0].out, expected[2].node, 4)
226 return trimTreeTestcase{
227 initial: graph,
228 expected: expected,
229 keep: keep,
230 }
231 }
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250 func createTestCase2() trimTreeTestcase {
251
252 graph := &Graph{make(Nodes, 5)}
253 nodes := graph.Nodes
254 for i := range nodes {
255 nodes[i] = createEmptyNode()
256 }
257 createEdges(nodes[3], nodes[1])
258 createEdges(nodes[1], nodes[2])
259 createEdges(nodes[2], nodes[0])
260 createEdges(nodes[0], nodes[4])
261 setEdgeWeight(nodes[3].Out, nodes[1], 12)
262 setEdgeWeight(nodes[1].Out, nodes[2], 8)
263 setEdgeWeight(nodes[2].Out, nodes[0], 15)
264 setEdgeWeight(nodes[0].Out, nodes[4], 10)
265
266
267 expected, keep := createExpectedNodes(nodes[3], nodes[4])
268 createExpectedEdges(expected[0], expected[1])
269 makeExpectedEdgeResidual(expected[0], expected[1])
270 setEdgeWeight(expected[0].out, expected[1].node, 10)
271 return trimTreeTestcase{
272 initial: graph,
273 expected: expected,
274 keep: keep,
275 }
276 }
277
278
279
280 func createTestCase3() trimTreeTestcase {
281 graph := &Graph{make(Nodes, 0)}
282 expected, keep := createExpectedNodes()
283 return trimTreeTestcase{
284 initial: graph,
285 expected: expected,
286 keep: keep,
287 }
288 }
289
290
291
292
293
294
295
296
297 func createTestCase4() trimTreeTestcase {
298 graph := &Graph{make(Nodes, 1)}
299 nodes := graph.Nodes
300 for i := range nodes {
301 nodes[i] = createEmptyNode()
302 }
303 expected, keep := createExpectedNodes(nodes[0])
304 return trimTreeTestcase{
305 initial: graph,
306 expected: expected,
307 keep: keep,
308 }
309 }
310
311 func createTrimTreeTestCases() []trimTreeTestcase {
312 caseGenerators := []func() trimTreeTestcase{
313 createTestCase1,
314 createTestCase2,
315 createTestCase3,
316 createTestCase4,
317 }
318 cases := make([]trimTreeTestcase, len(caseGenerators))
319 for i, gen := range caseGenerators {
320 cases[i] = gen()
321 }
322 return cases
323 }
324
325 func TestTrimTree(t *testing.T) {
326 tests := createTrimTreeTestCases()
327 for _, test := range tests {
328 graph := test.initial
329 graph.TrimTree(test.keep)
330 if !graphsEqual(graph, test.expected) {
331 t.Fatalf("Graphs do not match.\nExpected: %s\nFound: %s\n",
332 expectedNodesDebugString(test.expected),
333 graphDebugString(graph))
334 }
335 }
336 }
337
338 func nodeTestProfile() *profile.Profile {
339 mappings := []*profile.Mapping{
340 {
341 ID: 1,
342 File: "symbolized_binary",
343 },
344 {
345 ID: 2,
346 File: "unsymbolized_library_1",
347 },
348 {
349 ID: 3,
350 File: "unsymbolized_library_2",
351 },
352 }
353 functions := []*profile.Function{
354 {ID: 1, Name: "symname"},
355 {ID: 2},
356 }
357 locations := []*profile.Location{
358 {
359 ID: 1,
360 Mapping: mappings[0],
361 Line: []profile.Line{
362 {Function: functions[0]},
363 },
364 },
365 {
366 ID: 2,
367 Mapping: mappings[1],
368 Line: []profile.Line{
369 {Function: functions[1]},
370 },
371 },
372 {
373 ID: 3,
374 Mapping: mappings[2],
375 },
376 }
377 return &profile.Profile{
378 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
379 SampleType: []*profile.ValueType{
380 {Type: "type", Unit: "unit"},
381 },
382 Sample: []*profile.Sample{
383 {
384 Location: []*profile.Location{locations[0]},
385 Value: []int64{1},
386 },
387 {
388 Location: []*profile.Location{locations[1]},
389 Value: []int64{1},
390 },
391 {
392 Location: []*profile.Location{locations[2]},
393 Value: []int64{1},
394 },
395 },
396 Location: locations,
397 Function: functions,
398 Mapping: mappings,
399 }
400 }
401
402
403 func TestCreateNodes(t *testing.T) {
404 testProfile := nodeTestProfile()
405 wantNodeSet := NodeSet{
406 {Name: "symname"}: true,
407 {Objfile: "unsymbolized_library_1"}: true,
408 {Objfile: "unsymbolized_library_2"}: true,
409 }
410
411 nodes, _ := CreateNodes(testProfile, &Options{})
412 if len(nodes) != len(wantNodeSet) {
413 t.Errorf("got %d nodes, want %d", len(nodes), len(wantNodeSet))
414 }
415 for _, node := range nodes {
416 if !wantNodeSet[node.Info] {
417 t.Errorf("unexpected node %v", node.Info)
418 }
419 }
420 }
421
422 func TestShortenFunctionName(t *testing.T) {
423 type testCase struct {
424 name string
425 want string
426 }
427 testcases := []testCase{
428 {
429 "root",
430 "root",
431 },
432 {
433 "syscall.Syscall",
434 "syscall.Syscall",
435 },
436 {
437 "net/http.(*conn).serve",
438 "http.(*conn).serve",
439 },
440 {
441 "github.com/blahBlah/foo.Foo",
442 "foo.Foo",
443 },
444 {
445 "github.com/BlahBlah/foo.Foo",
446 "foo.Foo",
447 },
448 {
449 "github.com/BlahBlah/foo.Foo[...]",
450 "foo.Foo[...]",
451 },
452 {
453 "github.com/blah-blah/foo_bar.(*FooBar).Foo",
454 "foo_bar.(*FooBar).Foo",
455 },
456 {
457 "encoding/json.(*structEncoder).(encoding/json.encode)-fm",
458 "json.(*structEncoder).(encoding/json.encode)-fm",
459 },
460 {
461 "github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
462 "redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
463 },
464 {
465 "github.com/foo/bar/v4.(*Foo).Bar",
466 "bar.(*Foo).Bar",
467 },
468 {
469 "github.com/foo/bar/v4/baz.Foo.Bar",
470 "baz.Foo.Bar",
471 },
472 {
473 "github.com/foo/bar/v123.(*Foo).Bar",
474 "bar.(*Foo).Bar",
475 },
476 {
477 "github.com/foobar/v0.(*Foo).Bar",
478 "v0.(*Foo).Bar",
479 },
480 {
481 "github.com/foobar/v1.(*Foo).Bar",
482 "v1.(*Foo).Bar",
483 },
484 {
485 "example.org/v2xyz.Foo",
486 "v2xyz.Foo",
487 },
488 {
489 "github.com/foo/bar/v4/v4.(*Foo).Bar",
490 "v4.(*Foo).Bar",
491 },
492 {
493 "github.com/foo/bar/v4/foo/bar/v4.(*Foo).Bar",
494 "v4.(*Foo).Bar",
495 },
496 {
497 "java.util.concurrent.ThreadPoolExecutor$Worker.run",
498 "ThreadPoolExecutor$Worker.run",
499 },
500 {
501 "java.bar.foo.FooBar.run(java.lang.Runnable)",
502 "FooBar.run",
503 },
504 {
505 "(anonymous namespace)::Bar::Foo",
506 "Bar::Foo",
507 },
508 {
509 "(anonymous namespace)::foo",
510 "foo",
511 },
512 {
513 "cpp::namespace::Class::method()::$_100::operator()",
514 "Class::method",
515 },
516 {
517 "foo_bar::Foo::bar",
518 "Foo::bar",
519 },
520 {
521 "cpp::namespace::Class::method<float, long, int>()",
522 "Class::method<float, long, int>",
523 },
524 {
525 "foo",
526 "foo",
527 },
528 {
529 "foo/xyz",
530 "foo/xyz",
531 },
532 {
533 "com.google.perftools.gwp.benchmark.FloatBench.lambda$run$0",
534 "FloatBench.lambda$run$0",
535 },
536 {
537 "java.bar.foo.FooBar.run$0",
538 "FooBar.run$0",
539 },
540 }
541 for _, tc := range testcases {
542 name := ShortenFunctionName(tc.name)
543 if got, want := name, tc.want; got != want {
544 t.Errorf("ShortenFunctionName(%q) = %q, want %q", tc.name, got, want)
545 }
546 }
547 }
548
View as plain text